feat(intelligence): OS-intelligence integrity — no-clobber merge + per-category freshness#745
Merged
Merged
Conversation
A degraded collection (SSH connects but individual probes fail or are sudo-denied) used to overwrite previously-good data with empty values, and both current-state stores keep no history. Fix: absence-of-observation is now distinct from observed-empty, keyed on whether the probe actually ran. - Discovery: each probe records its fact CATEGORY in SystemFacts.Observed; persist() reads the prior host_system_info row in-tx and carries forward the prior value for any unobserved category (observed values, even 0/false, write normally). Correctly handles cases a naive COALESCE can't (a genuinely full disk, apparmor=false). Spec system-host-discovery v1.5.0 (C-08, AC-24). - OS intelligence: RunCycle merges prior into the snapshot for unobserved categories BEFORE both Diff and persist, so a failed probe neither emits a false change event nor blanks the stored snapshot. Observed set is transient (json:"-"). Spec system-os-intelligence v1.2.0 (C-03, AC-18). - Verified the intelligence scheduler's recordSuccess only touches next_intelligence_at and never clobbers the snapshot.
Phase 1b of the last-known-good model: the no-clobber merge keeps a failed probe
from blanking good data; this records WHEN each carried-forward value was last
actually seen, so a consumer can tell fresh data from stale.
- Migration 0052 adds category_freshness JSONB to host_system_info and
host_intelligence_state: one key per fact category ->
{observed_at, attempt_at, status}. status ok = observed this run; stale = not
observed but has a prior observation (prior observed_at kept, attempt advances).
- Discovery persist and the collector state write compute and store it from the
Observed set + the prior freshness (computeFreshness / computeSnapFreshness).
- Specs: system-host-discovery v1.6.0 (C-13, AC-25), system-os-intelligence
v1.3.0 (C-09, AC-19). DB + unit tests.
The API surfacing + freshness-aware UI (render a stale value as 'unknown, last
good X ago') is the remaining Phase 1b sub-step.
Contributor
Author
|
Added Phase 1b (backend): per-category collection freshness.
Remaining Phase 1b sub-step (not in this PR yet): API exposure + freshness-aware UI (render a stale value as "unknown, last good X ago"). |
…+ System card
Expose the category_freshness map (migration 0052) on the two host read
endpoints and render staleness in the host detail System card so an
operator can tell a freshly-observed value from a carried-forward one.
Backend:
- GET /api/v1/hosts/{id}/system-info now selects category_freshness and
exposes it as CategoryFreshness (omitted when NULL, pre-0052 rows).
- GET /api/v1/intelligence/state/{host_id} does the same for the
intelligence snapshot's per-category freshness.
- Shared FreshnessEntry {status, observed_at, attempt_at} + CategoryFreshness
map added to api/openapi.yaml; server.gen.go + schema.d.ts regenerated.
Frontend:
- CardSystem renders a muted "Last verified <relative>" caveat beneath a
value whose category is stale (Distribution/FQDN/Firewall/Disk/Memory);
ok/absent freshness renders nothing.
Specs: api-host-system-info v1.1.0 (C-05/AC-06), api-os-intelligence v1.1.0
(C-06/AC-11), frontend-host-detail-system-card v1.1.0 (C-07/AC-08). All three
at 100% annotation coverage; specter check + check --test clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OS intelligence and discovery collect host facts over SSH but were a lossy cache: a degraded collection (SSH connects, but individual probes fail or are sudo-denied) overwrote previously-good data with empty values, and both current-state stores keep no history.
This branch delivers Phase 1 (no-clobber merge), Phase 1b-backend (per-category freshness persistence), and Phase 1b-frontend (freshness exposed on the read APIs + rendered in the host System card) of the OS-intelligence temporal / last-known-good model (design doc, engineering-local).
Phase 1 — no-clobber merge
host_system_info): each probe records its fact category inSystemFacts.Observed;persist()reads the prior row in-tx and carries forward the prior value for any unobserved category. Observed values (even0/false) write normally — a genuinely full disk (free=0),apparmor_enabled=false, "0 services" are preserved where a naiveCOALESCEwould lose them. Specsystem-host-discoveryv1.5.0 (C-08, AC-24).host_intelligence_state):RunCyclemerges prior into the snapshot for unobserved categories before bothDiffand persist, so a failed probe neither emits a false change event nor blanks the stored snapshot. Specsystem-os-intelligencev1.2.0 (C-03, AC-18).Phase 1b-backend — per-category freshness
category_freshness JSONBto both tables: per category{observed_at, attempt_at, status}(ok = observed this run; stale = carried-forward, last-goodobserved_atkept).computeFreshness(discovery) +computeSnapFreshness(collector). Specssystem-host-discoveryv1.6.0 (C-13, AC-25),system-os-intelligencev1.3.0 (C-09, AC-19).Phase 1b-frontend — surfacing
GET /api/v1/hosts/{id}/system-infoandGET /api/v1/intelligence/state/{host_id}now expose the freshness map as a sharedCategoryFreshnessschema (omitted for pre-0052 NULL rows). Specsapi-host-system-infov1.1.0 (C-05, AC-06),api-os-intelligencev1.1.0 (C-06, AC-11).frontend-host-detail-system-cardv1.1.0 (C-07, AC-08).Testing
go build ./...· vet clean · frontendtsc --noEmitclean · prettier clean.specter check116/116;specter check --test0 errors;specter coverage --strictness annotation100% (touched specs:system-host-discovery,system-os-intelligence,api-host-system-info6/6,api-os-intelligence11/11,frontend-host-detail-system-card8/8).server.gen.go+schema.d.tsregenerated with pinned oapi-codegen v2.7.0 (deterministic; verified re-run identical).TestAPI_HostSystemInfo_GET_ExposesCategoryFreshness(AC-06),TestAPI_Intelligence_State_ExposesCategoryFreshness(AC-11),TestDiscover_NoClobberOnPartialCollection(AC-24),TestDiscover_CategoryFreshness(AC-25),TestMergeUnobserved_NoClobberAndNoFalseEvents(AC-18),TestComputeSnapFreshness(AC-19); frontendhost-detail-system-card8/8 (AC-08 freshness caveat +formatTimeAgobuckets).Not in this PR
Phase 2 (temporal fact-history substrate: append-on-change + point-in-time) and Phase 3 (drift / fleet correlation / CVE / baselines) follow, per the design doc.