You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decide how the model meets human access grants and Grafana content #357
How does the model express access granted to people rather than to Processes, and observability content that is not a workload?
headlamp binds cluster-admin to the OIDC group oidc:k8s-admin (utility-system/headlamp/oidc-admin-binding.yaml). That is a cluster role held by a human group, which the api admission (0092) has no word for. The observability Project carries 32 GrafanaDashboards, 14 GrafanaFolders, PrometheusRules and an AlertmanagerConfig. Chapter 30 ledgers them but does not model them.
Settled with the maintainer on 2026-10-09. Input: the live objects in JorisJonkers-dev/fleet-infra at 4e4a9bb (utility-system/headlamp/oidc-admin-binding.yaml, grafana-dashboards/, observability-rules/) and docs/research/live-estate-needs.md.
What is live
One human grant.headlamp-oidc-admin binds cluster-admin to the Group oidc:k8s-admin. auth-api decides who is in the group, and the k3s server flag --oidc-groups-prefix=oidc: (a bootstrap fact) adds the prefix. kubectl over OIDC uses the same binding, so the grant is not headlamp's.
Grafana content. 32 GrafanaDashboards (23 with inline JSON, 9 referencing grafana.com by id and revision and fetched at runtime), 14 topical GrafanaFolders, and one Grafana CR that points the operator at the chart's Grafana with the chart's admin Secret. service-overview and service-template already select the service through a $service variable.
Alerting. One PrometheusRule, platform-alerts, holding 40 platform-wide alerts (ingress, SLO burn, pods, JVM, data tier, nodes, Flux, Vault, backups). One AlertmanagerConfig routes critical and warning to a Discord receiver, whose webhook arrives through VSO.
A misfit in chapter 30. It says the dashboards "become Assets", but an Asset is a file mounted into a Process (0014), and a dashboard is mounted into nothing.
Decisions
A human grant is declared in the Platform document. Who may administer the cluster is contended for by every project, so by 0004 it is the platform's. It is not on headlamp: the grant is to people, and removing headlamp must not drop kubectl access. A grant is {group, clusterRole, reason} and renders one ClusterRoleBinding. Its subject is a group only, never a User. Its role is one of Kubernetes' built-in ClusterRoles (cluster-admin, admin, edit, view), and it is always cluster-wide. Nothing scoped and no authored rules: nothing live needs either, and API access for a Process stays 0092's. The group is written exactly as the API server sees it (oidc:k8s-admin). The prefix stays a bootstrap fact and is not restated in the model.
A Dashboard is a new concept, not an Asset. It is a Grafana dashboard JSON file beside the project file, declared as dashboards: [{from}] on any Application and rendered as a GrafanaDashboard by a grafana adapter. Application dashboards live with their Application. Platform dashboards live on the observability Applications. The target is the operator's CR, so this holds however the foundation's charts are rendered. Chapter 30's "become Assets" is corrected.
The folder is derived from the Project. The adapter renders one GrafanaFolder per Project that has dashboards, and nothing is authored. The 14 live topical folders regroup by Project.
grafana.com dashboards are vendored. The 9 are downloaded once and committed as JSON like any other dashboard, so a pinned render pins their content and a diff shows a change. Nothing is fetched from grafana.com at runtime.
service-overview and service-template are plain platform dashboards. Chapter 30's per-Application derivation from the scrape surface is struck. The model has no dashboard DSL anywhere.
Dashboard uids are checked at build. A dashboard file without a uid, or a uid used twice across the estate, is a build error. Otherwise the two dashboards overwrite each other silently, in arrival order.
Alert rules are rule-group files on the monitoring stack only. A Prometheus rule-group file beside the project file, alertRules: [{from}], renders a PrometheusRule. Only Applications the Platform document names as the monitoring stack may carry one. Chapter 10's line holds: rules are the monitoring stack's, and an ordinary Application never carries PromQL.
The Platform document names the monitoring stack by role.monitoring: {grafana, prometheus, alertmanager}, each naming an Application. This one declaration admits those Applications to carry alert rules. It also gives the grafana adapter its instance target (the Grafana CR), the PrometheusRule selector and the AlertmanagerConfig's namespace. An unknown or missing name is a build error.
Receivers and severity routes are declared in the Platform document. Chapter 10 already says a receiver is a shared channel and platform-assigned. It renders the AlertmanagerConfig, and the Discord webhook arrives through VSO, the one secret delivery.
alertClass is dropped. Live alerts route by rule severity to one channel, and no consumer reads the class: the monitoring stack is now deploy-kit's own render, not a reader of the projection. observability shrinks to scrape. 0025, E_ALERT_CLASS_WITHOUT_SIGNAL and the Alert Class term are rewritten or removed with the spec rewrite. This feeds Decide what is cut, merged or deferred from the spec and the register.
Leaves open
How Grafana, Prometheus and Alertmanager are rendered (charts or Processes), and where the Grafana CR's admin credential comes from, belong to Decide how the foundation's HelmReleases are rendered and owned. Nothing here depends on that answer, because every object above is an operator CR.
Record
This answer is recorded here, on the ticket, the same way as its parent. The new terms (Human Grant, Dashboard, Alert Rules, the monitoring role map) enter CONTEXT.md, spec/v1 (chapters 10, 14, 20 and 30) and their ADRs with the spec rewrite. Class C in chapter 30 empties.
Hand-off from Decide how the foundation's HelmReleases are rendered and owned: this ticket also decides where kube-prometheus-stack's 31 upstream PrometheusRule groups (the kubernetes-mixin alerts) go, since they are the same question as dashboards, and whether grafana-operator stays. Headlamp's chart becomes a utility Application; its cluster-admin binding is already this ticket's.
C43, a dashboard without a uid is the same hazard as two dashboards sharing one. It is a different hazard. grafana-operator falls back to the object's Kubernetes UID, so nothing is overwritten; instead the dashboard's address changes whenever the object is recreated, for example by a namespace Move. The uid check keeps a reason, but not the one stated.
Two more claims hold only with a condition: every vendored grafana.com dashboard needs its ${DS_PROMETHEUS} input resolved (C44), and one AlertmanagerConfig routes the whole estate only when the Alertmanager sets alertmanagerConfigMatcherStrategy.type: None (C45).
The failed claim flagged above is decided in Decide what each settled decision does about the claim under it that failed. Item 6 stands, with two reasons: a uid used twice overwrites silently, and a missing uid gives the dashboard an address that changes whenever its object is recreated. The adapter derives no uid.
Activity
ExtraToast commentedon Oct 9, 2026
Resolution
Settled with the maintainer on 2026-10-09. Input: the live objects in
JorisJonkers-dev/fleet-infraat4e4a9bb(utility-system/headlamp/oidc-admin-binding.yaml,grafana-dashboards/,observability-rules/) and docs/research/live-estate-needs.md.What is live
headlamp-oidc-adminbindscluster-adminto the Groupoidc:k8s-admin. auth-api decides who is in the group, and the k3s server flag--oidc-groups-prefix=oidc:(a bootstrap fact) adds the prefix. kubectl over OIDC uses the same binding, so the grant is not headlamp's.GrafanaDashboards (23 with inline JSON, 9 referencing grafana.com by id and revision and fetched at runtime), 14 topicalGrafanaFolders, and oneGrafanaCR that points the operator at the chart's Grafana with the chart's admin Secret.service-overviewandservice-templatealready select the service through a$servicevariable.PrometheusRule,platform-alerts, holding 40 platform-wide alerts (ingress, SLO burn, pods, JVM, data tier, nodes, Flux, Vault, backups). OneAlertmanagerConfigroutescriticalandwarningto a Discord receiver, whose webhook arrives through VSO.Decisions
{group, clusterRole, reason}and renders oneClusterRoleBinding. Its subject is a group only, never a User. Its role is one of Kubernetes' built-in ClusterRoles (cluster-admin,admin,edit,view), and it is always cluster-wide. Nothing scoped and no authored rules: nothing live needs either, and API access for a Process stays 0092's. The group is written exactly as the API server sees it (oidc:k8s-admin). The prefix stays a bootstrap fact and is not restated in the model.dashboards: [{from}]on any Application and rendered as aGrafanaDashboardby agrafanaadapter. Application dashboards live with their Application. Platform dashboards live on the observability Applications. The target is the operator's CR, so this holds however the foundation's charts are rendered. Chapter 30's "become Assets" is corrected.GrafanaFolderper Project that has dashboards, and nothing is authored. The 14 live topical folders regroup by Project.service-overviewandservice-templateare plain platform dashboards. Chapter 30's per-Application derivation from the scrape surface is struck. The model has no dashboard DSL anywhere.uids are checked at build. A dashboard file without auid, or auidused twice across the estate, is a build error. Otherwise the two dashboards overwrite each other silently, in arrival order.alertRules: [{from}], renders aPrometheusRule. Only Applications the Platform document names as the monitoring stack may carry one. Chapter 10's line holds: rules are the monitoring stack's, and an ordinary Application never carries PromQL.monitoring: {grafana, prometheus, alertmanager}, each naming an Application. This one declaration admits those Applications to carry alert rules. It also gives thegrafanaadapter its instance target (theGrafanaCR), thePrometheusRuleselector and theAlertmanagerConfig's namespace. An unknown or missing name is a build error.AlertmanagerConfig, and the Discord webhook arrives through VSO, the one secret delivery.alertClassis dropped. Live alerts route by rule severity to one channel, and no consumer reads the class: the monitoring stack is now deploy-kit's own render, not a reader of the projection.observabilityshrinks toscrape. 0025,E_ALERT_CLASS_WITHOUT_SIGNALand the Alert Class term are rewritten or removed with the spec rewrite. This feeds Decide what is cut, merged or deferred from the spec and the register.Leaves open
How Grafana, Prometheus and Alertmanager are rendered (charts or Processes), and where the
GrafanaCR's admin credential comes from, belong to Decide how the foundation's HelmReleases are rendered and owned. Nothing here depends on that answer, because every object above is an operator CR.Record
This answer is recorded here, on the ticket, the same way as its parent. The new terms (Human Grant, Dashboard, Alert Rules, the monitoring role map) enter
CONTEXT.md,spec/v1(chapters 10, 14, 20 and 30) and their ADRs with the spec rewrite. Class C in chapter 30 empties.ExtraToast commentedon Oct 9, 2026
Hand-off from Decide how the foundation's HelmReleases are rendered and owned: this ticket also decides where kube-prometheus-stack's 31 upstream PrometheusRule groups (the kubernetes-mixin alerts) go, since they are the same question as dashboards, and whether
grafana-operatorstays. Headlamp's chart becomes a utility Application; its cluster-admin binding is already this ticket's.ExtraToast commentedon Oct 12, 2026
A claim behind this decision that failed a source check
Find the claims behind the settled decisions that no source supports traced each third-party claim this resolution rests on to a primary source. One fails. This comment flags it; it does not reopen the decision. The evidence is in
docs/research/unsupported-claims.md, by claim id.uidis the same hazard as two dashboards sharing one. It is a different hazard. grafana-operator falls back to the object's Kubernetes UID, so nothing is overwritten; instead the dashboard's address changes whenever the object is recreated, for example by a namespace Move. Theuidcheck keeps a reason, but not the one stated.Two more claims hold only with a condition: every vendored grafana.com dashboard needs its
${DS_PROMETHEUS}input resolved (C44), and one AlertmanagerConfig routes the whole estate only when the Alertmanager setsalertmanagerConfigMatcherStrategy.type: None(C45).ExtraToast commentedon Oct 12, 2026
The failed claim flagged above is decided in Decide what each settled decision does about the claim under it that failed. Item 6 stands, with two reasons: a
uidused twice overwrites silently, and a missinguidgives the dashboard an address that changes whenever its object is recreated. The adapter derives nouid.