package ontology // Monitoring resolution: which check kinds an entity type warrants. // // Coverage is not uniform. A `service` warrants an HTTP probe; a `site` is a // physical location with nothing to probe; a `dns-zone` warrants a check whose // checker does not exist yet. Collapsing those three into "has no check_def" // is what made the fleet's monitoring gap invisible — 86 of 89 active entities // had no check, and staleSweep's INNER JOIN against check_defs meant none of // them could ever be marked stale. // // So the declaration lives on the entity TYPE, in seeds/ontology.yaml, and // resolves through the same is-a hierarchy the validator already walks: // declaring `monitoring: [ping, resource]` on abstract `machine` covers // proxmox-host, standalone-server, workstation and appliance. // MonitoringResolution is the outcome of resolving a type's monitoring // declaration. The three states are deliberately distinguishable: // // Declared=false — nobody in the chain said anything. An ontology // gap: report it, but as a modelling problem // rather than as a fleet monitoring problem. // Declared=true, len(0) — explicitly unmonitorable. Working as intended; // never raise an `unmonitored` signal for it. // Declared=true, len(n) — these kinds are expected to exist. type MonitoringResolution struct { Kinds []string // Declared reports whether anything in the chain (or the layer default) // settled the question. Declared bool // Source names the type that supplied the answer — the type itself, an // ancestor, or "" when the layer default applied. Useful in log lines // that explain why an entity has the checks it has. Source string } // None reports an explicit "this type is not monitored". func (m MonitoringResolution) None() bool { return m.Declared && len(m.Kinds) == 0 } // Wants reports whether the type expects a check of this kind. func (m MonitoringResolution) Wants(kind string) bool { for _, k := range m.Kinds { if k == kind { return true } } return false } // Monitoring resolves the check kinds a type warrants, walking parent types // until one carries a declaration. // // Types outside the infrastructure layer (governance, cognition, meta) fall // back to an implicit "none": a signal, an approval, a document and a person // are records, not running things. That default keeps ~20 record types out of // the ontology without needing an explicit `monitoring: none` on each, while // still treating an undeclared *infrastructure* type as a genuine gap — those // are the ones somebody should have made a decision about. A non-infrastructure // type that really is probeable (agent, which serves a gateway on :8092) just // declares its kinds explicitly and wins on the first rule. func (t *TypeTree) Monitoring(typ string) MonitoringResolution { seen := map[string]bool{} for cur := typ; cur != ""; cur = t.Types[cur].Parent { info, ok := t.Types[cur] if !ok { break } if seen[cur] { break // cycle guard — ingest rejects cycles, belt and braces } seen[cur] = true if info.Monitoring != nil { return MonitoringResolution{ Kinds: *info.Monitoring, Declared: true, Source: cur, } } } if info, ok := t.Types[typ]; ok && info.Layer != "infrastructure" { return MonitoringResolution{Declared: true} } return MonitoringResolution{} }