From c9a00a95325e28b7e3acf2414d8ba52ae81d3879 Mon Sep 17 00:00:00 2001 From: dtoro Date: Wed, 29 Jul 2026 23:47:43 +0200 Subject: [PATCH] feat(checks): per-entity monitoring override; service:haos opts out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `monitoring` attribute on an entity now overrides its type's declaration: "none" opts out, a list overrides the kinds. service:haos uses it to opt out — haos blocks SSH (no process probe can reach it) and the VM is already covered by vm:haos's vm-status check, so the redundant process check only ever reported false-down. vm:haos -> service:haos via provides confirms the coverage. --- internal/checkdefaults/defaults.go | 33 ++++++++++++++++++++++++++++++ seeds/inventory.yaml | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/internal/checkdefaults/defaults.go b/internal/checkdefaults/defaults.go index be223a6..85e2a93 100644 --- a/internal/checkdefaults/defaults.go +++ b/internal/checkdefaults/defaults.go @@ -110,6 +110,18 @@ func Ensure(ctx context.Context, tx pgx.Tx, tree *ontology.TypeTree, t Target) ( attrs = map[string]any{} } + // Per-entity override: an explicit `monitoring` attribute wins over the + // type declaration. A single entity can opt out (monitoring: none) or pick + // different kinds without introducing a new type — e.g. service:haos opts + // out because its VM is already covered by a vm-status check and the + // service can't be SSH-probed (haos blocks SSH). + if mo, ok := attrs["monitoring"]; ok { + mon = resolveMonitoringAttr(mo, mon) + if mon.None() { + return res, nil + } + } + // A service has no address of its own — it lives on the container that // provides it. Fall back to the graph before giving up. host := resolveHost(attrs) @@ -148,6 +160,27 @@ func Ensure(ctx context.Context, tx pgx.Tx, tree *ontology.TypeTree, t Target) ( return res, nil } +// resolveMonitoringAttr turns an entity's `monitoring` attribute into a +// MonitoringResolution that overrides the type's declaration. Accepts the +// scalar "none" (or empty) to opt out, or a list of kind strings to override. +func resolveMonitoringAttr(v any, fallback ontology.MonitoringResolution) ontology.MonitoringResolution { + switch vv := v.(type) { + case string: + if vv == "none" || vv == "" { + return ontology.MonitoringResolution{Declared: true, Source: "attribute"} + } + case []any: + kinds := make([]string, 0, len(vv)) + for _, k := range vv { + if s, ok := k.(string); ok && s != "" { + kinds = append(kinds, s) + } + } + return ontology.MonitoringResolution{Declared: true, Kinds: kinds, Source: "attribute"} + } + return fallback +} + // buildKind turns one declared semantic kind into concrete check_defs, or // returns the reason it could not. func buildKind(kind string, t Target, attrs map[string]any, host, user string, port int) ([]checkDef, string) { diff --git a/seeds/inventory.yaml b/seeds/inventory.yaml index a4fae62..56c3569 100644 --- a/seeds/inventory.yaml +++ b/seeds/inventory.yaml @@ -265,7 +265,8 @@ entities: attributes: {url: "https://zimaos.hubris.network", doc_page: knowledge/wiki/vms/100-zimaos.md}} - {slug: "service:haos", type: service, name: haos, - attributes: {doc_page: knowledge/wiki/vms/108-haos.md}} + attributes: {doc_page: knowledge/wiki/vms/108-haos.md, + monitoring: none}} # redundant: vm:haos covers liveness via vm-status; haos blocks SSH so a process check can't reach it - {slug: "service:teddycloud", type: service, name: teddycloud, attributes: {url: "https://teddy.hubris.network", doc_page: knowledge/wiki/containers/131-teddycloud.md,