Watch
1
0
Fork
You've already forked RedFlag
0
Commit graph RedFlag/server/internal/models
Author SHA1 Message Date
Fimeg
1f75bfd23a device-type: layered detection + laptop/vm/container types
Replaces the flat battery x display matrix (which misclassified laptops
as phones) with layered detection: container -> vm -> SMBIOS chassis ->
ARM fallback. Adds laptop, vm, container device types across agent,
migration 062, server validation, web icons/types.
2026-07-12 14:55:04 -04:00
Fimeg
ff2f30f47a v0.2.9.3: device classification + ARM support — Pixel 3 lands
DEVICE-002: ARM machine-ID fallback — device-tree model + /etc/machine-id
combo, then /proc/cpuinfo Serial (all-zero rejected), before the weak
hostname fallback. Hardware-bound IDs on DMI-less devices.

DEVICE-001: agent detects device_type (server/desktop/phone/tablet) from
/sys signals — system battery (scope=Device peripherals excluded, UPS
excluded), DRM connector state, framebuffer min-dimension for phone/tablet
split. Reports device_type/device_model/os_distro in registration and
system-info paths.

SERVER-001: migration 061 — device_type, device_type_manual (operator
override, never agent-written), device_model, os_distro on agents.
effective_device_type computed into every serialized agent.

SERVER-002: PUT /admin/agents/:id/device-type — set/clear override,
enum-validated, journaled.

WEB-001: device-type icons + fleet filter, device model in list, detail
header badge with reclassify dropdown, os_distro surfaced.

INSTALL-003: arm64 install path unblocked — helper (required manifest
component) now cross-built aarch64-unknown-linux-musl via rust-lld in the
server image, signed at boot (helperArches += arm64), listed in the release
manifest. Install template already handled uname -m and pacman.

Plus in-flight: desktop tray wiring, enrollment page polish, CI workflow
updates, RAF session-broker/pacman-scanner docs, native installer scaffold.
2026-07-06 18:21:23 -04:00
Fimeg
9e4d59695f upstream: track prereleases per row; rename gitea adapter to forgejo
Most forges hand back a "latest stable" that ignores prereleases. Fine
until something ships only prereleases for a stretch — us, through
alpha, where every tag under v0.3.0 goes out as a prerelease. Against
that, /releases/latest either freezes or returns nothing, and the
dashboard reads like nothing's moving.

Add a per-row track_prereleases flag. When it's on, the forgejo adapter
walks the full release list and considers prereleases when picking the
highest version; off (the default) keeps stable-only behavior, so the
other adapters don't change. Seed our own self-row on so we stop
looking frozen.

Renamed gitea_releases to forgejo_releases while in there — the wire
format is Forgejo's, Codeberg runs it, and the old name was a misnomer.
Legacy source="gitea" rows still resolve through an alias.
2026-06-15 12:10:12 -04:00
Fimeg
2064a5035f supply chain vuln UI — backend endpoints + frontend rendering for CVE/advisory detail 2026-06-11 11:32:24 -04:00
Fimeg
c71fc093db refactor: vulnerability → security advisory terminology
Rename CVE/vulnerability language to advisory/threat/fix across the stack:
- Dashboard: installed_cve_count→open_threat_count, security_update_count→available_fix_count
- Update detail: Known Vulnerabilities→Security Advisories
- AdvisoryType() helper for human-readable advisory ID prefixes
- clearVulnsOnInstall on installed transition with per-advisory security event logging
- StatsHandler takes checkInInterval for online/offline threshold
- AttentionPanel re-keyed on open-threats / available-fixes
2026-06-11 02:01:43 -04:00
Fimeg
244d9091ee feat: process explorer — on-demand /proc scanning with full osquery parity
Agent-side: reads /proc for all PIDs with 25+ fields (identity, resources,
state, disk I/O, elevation) plus related data on drill-down (open files,
sockets, pipes, env keys, memory map, namespaces, listening ports). Pure
/proc reads, no subprocess spawns.

Server-side: dedicated tables (agent_process_snapshots, agent_processes,
agent_process_related) with JSONB for related data. On-demand scan via
scan_processes command, last-10-snapshot retention. Four endpoints:
report, get latest, get detail, trigger scan.

UI: new Processes tab in agent detail with sortable/filterable table,
search by name/cmdline, state/user filters, and ProcessDetailModal with
tabs for Overview, Network, Files, Environment, Memory, Namespaces.
2026-06-11 02:01:43 -04:00
Fimeg
b9e083c4f7 test+fix: supply-chain consumer/OSV coverage; local-approve and update handler work
OSV fail-closed paths, safeTokenFilename traversal, bind check,
disallowed package types covered in agent/internal/supplychain.
Artifact hash resolution and dnf scanner test fixes. Server update
handlers extended for the local-approve flow with OSV version test
coverage.
2026-06-11 02:01:42 -04:00
Fimeg
7cbf174652 feat: unified event timeline, desktop bundling, UI primitives
Unified timeline:
- client_errors bridge to system_events (component='client')
- Admin action audit middleware on /admin/* routes
- History page filters: type, severity dropdowns
- ChatTimeline accepts externalType/externalSeverity props

Desktop app bundling:
- Docker: Tauri builder stage (Rust + Node + webkit2gtk)
- Server signs desktop binary at startup, serves via /api/v1/desktop/:arch
- Install script downloads + verifies desktop binary (Step 7c)
- Agent spawns desktop as child process, monitors + restarts on crash
- Desktop config: enabled, max_restarts, restart_delay_sec
- Session detection: /proc environ scan (Linux), query session (Windows)
- Desktop health: POST /v1/desktop every 30s from tray app
- /v1/status includes desktop running/pid/enabled state

UI primitives:
- Modal, PageState, Pagination, StatCard components
- Dashboard, Updates, Agents pages refactored to use primitives
- Novell aesthetic preserved throughout
2026-06-08 19:09:58 -04:00
Fimeg
ffffe9b956 feat: FEAT-002 local agent API, desktop tray spine, screenshot handler
Slices 1-3 of the local agent IPC surface:
- Read model (local_status.go, loop wired): update counts, scanner status,
  check-in state, token receipt counts — no token material exposed
- Local IPC (localapi/): Unix socket (group=redflag-local, 0660) + Windows
  named pipe (SDDL: LocalSystem/Admins/RedFlagLocal); five read-only endpoints
- `redflag-agent -local-status` CLI probe of the local API surface
- Screenshot capture handler (screenshot.go, dispatch wired)
- Tauri desktop spine (desktop/): tray icon, left-click window, local IPC reader
- Desktop React entry (web/src/desktop/LocalAgentApp.tsx, index.desktop.html,
  vite.desktop.config.ts)
- Installer group provisioning: linux.sh creates redflag-local, sets
  SupplementaryGroups; windows.ps1 creates RedFlagLocal security group
- Server-side: screenshot receipt handler on agents, updates handler additions
- web/package.json: @tauri-apps/api + tauri CLI dev dep added
2026-06-08 17:08:58 -04:00
Fimeg
5b1a16ca3e v0.2.6.2 — osv scans moved to detection, soak gate grew up
approval stopped re-scanning osv; it just reads what detection already
found. soak gate + age gate are real settings now (env→db→default), and
the dead soak-override column + table got composted.
2026-06-07 14:44:41 -04:00
Fimeg
b82649967e RECONCILE-001: scan-set closure (close-by-absence) + v0.2.6.1
Treats each ecosystem scan as the authoritative full set for that
(agent, ecosystem) pair. Packages absent from a successful scan that
are still in a waiting state (pending/approved) are closed to installed
with out-of-band provenance — no operator action required.

State machine:
- Added pending/approved → installed edges (out-of-band resolution path)
- Added installed → pending edge (reactivation when a new version reappears)
- ReconcileFromScan updated to match: installed now reopens, ignored/failed preserved

Server (ReportUpdates):
- closeScanAbsentRows goroutine: diff waiting rows against reported set,
  transition absent rows via transitionStatus (guarded UPDATE, idempotent)
- Provenance stamping: redflag_receipt if a consumed capability token exists,
  out_of_band otherwise
- System event emitted per closure for audit trail
- scanEcosystemSupported gate: dnf/apt only; failed/partial scans never close rows

Agent:
- UpdateReport extended with Ecosystem + ScanSucceeded fields
- APT/DNF scan handlers now always report on successful scan (even 0 updates)
- HandleScanAPT/DNF/Updates: report failure is non-fatal (transport problem,
  scan succeeded locally)

Queries:
- GetTrackedNonResting: scoped to pending/approved only — in-flight states
  (checking_dependencies, pending_dependencies, installing) are orchestrator-owned
- TransitionByID: routes closure through the state machine
- HasConsumedTokenForUpdate: provenance check for the reconciler
- UpdateCurrentStateInTx SQL CASE: installed now reopens to pending on re-scan

Tests: reconcile_test.go (5 unit tests including load-bearing
TestWaitingStatesResolveOutOfBand), reconcile_test.go handler tests (7 sub-tests).

Bump: v0.2.6.1
2026-06-06 20:46:29 -04:00
Fimeg
1ec7999d8f fix: code review batch 1 + dead store setting
- #1: ReconcileAll goroutine uses context.Background, not request ctx
- #4: InstallVersion now checks maintenance window before dry-run
- #5+#23: tickAliases fetches stale slugs once before loop; rename shadowing var
- #6: UpsertReconciled sql.ErrNoRows on manual conflict is a no-op, not a warn
- #7: normalizeRepoToEcosystem uses ordered slice, not non-deterministic map
- #8: MatchByContainer ILIKE escapes % and _ metacharacters via REPLACE
- #9: EnqueueDryRun uses target_version key for selected_version, keeps available_version for freshness
- #13: InstallVersion drops second GetUpdateByID, mutates struct locally
- #14: EnrichFromMetadata reserved map is package-level var, not per-call alloc
- #21: recordGateOverride shared helper; recordSupplyChain/SoakOverride delegate to it
- store.ts: remove dead notificationsEnabled setting (no callers outside store)
2026-06-05 21:26:44 -04:00
Fimeg
f005255e68 feat: docker enrichment pipeline, package detail, update history pagination 2026-06-05 21:18:06 -04:00
Fimeg
d1424e8377 feat: FEAT-002 metadata pipeline, GATE-005 soak gate, BRIDGE-001 auto-discovery
FEAT-002: Server-side metadata pipeline
- ReportUpdates merges PackageDescription/CVEList/KBID/SizeBytes into metadata JSONB
- UpdateCurrentStateInTx uses JSONB merge (||) instead of replace
- Fixes current_version bug (missing EXCLUDED.current_version)
- EnrichFromMetadata() on UpdateState populates display fields from metadata
- mergedVulnerabilities() deduplicates agent CVEs with OSV.dev results
- VulnerabilityEntry struct for unified vulnerability representation

GATE-005: Version soak-gate
- Migration 050: selected_version column, version_soak_overrides table
- soak_gate.go: SoakGateConfig/EvaluateSoakGate (14-day default, block enforcement)
- POST /updates/:id/install-version endpoint with soak evaluation
- EnqueueDryRun reads COALESCE(selected_version, available_version)
- Override journals to system_events (recordSoakOverride)

BRIDGE-001: Tracked software auto-discovery
- Migration 051: repology_slug/container_image_pattern/binary_probe on tracked_software,
  repology_aliases table, match_method/package_name on agent_tracked_software
- RepologyCache: fetches /api/v1/project/{slug}/packages, normalizes repos to ecosystems
- ReconciliationQueries: MatchByRepology/MatchByContainer/MatchByExactName
- Reconciler: cascade matching service with hourly loop, hooks into ReportUpdates
- UpsertReconciled: preserves manual operator bindings
- COMMON_SEEDS updated with repology_slug values
- Syncer updated with 24h alias refresh ticker
2026-06-05 17:43:11 -04:00
Fimeg
62f2764260 v0.2.5.1: lifecycle, live operations, unified history
Lifecycle:
- ReopenUpdate + ResolveUpdate replace RetryUpdate; routes for
  GET /updates/:id/lifecycle and POST reopen/resolve
- confirmUpdateCommand marks update_agent completed on version attestation
- migration 048: started/running added to update_logs.result
- helper atomic_replace_binary: copy-to-sibling then rename() (ETXTBSY)

Live operations:
- event_stream service + /events endpoints, LiveOperations page
- capability-token queries for the live view

History + CVE:
- History page reads /events/recent: filterable lifecycle/command/
  system/orchestrator timeline with agent crosslinks
- CVE drill-down: OSV parse carries CVSS vector, fixed version, published
  date, severity; issuer-linked aliases (CVE->NVD, GHSA->GitHub, ALSA->errata)
- DependencyClosureTree: one shared closure component in update detail
- STARTED (blue spinner) and PARTIAL (amber) result badges
2026-06-05 09:13:42 -04:00
Fimeg
5758b26875 swap uuid lib, windows installer pass, README/RAF copy
- google/uuid -> gofrs/uuid/v5 across server + agent
- windows.go: cross-platform binding cleanup
- linux install template: disable sudo lecture for TTY-less service user
- README: XZ/SolarWinds lede, stable-release note, single attack-surface block
2026-06-03 15:39:49 -04:00
Fimeg
dc3bfdf493 supply chain gate: a vuln is a full stop now — admin signs for it or it doesn't ship
delivery plumbing that got us there:
- acks clear on result-recorded, not command lifecycle status (no more 34-deep recycling)
- timeouts, cancels, dropped acks/receipts, failed actions all land in history instead of dying on stdout
- one shared closure-cleared predicate so auto-confirm and manual approve can't drift

override waives the vuln call only and gets journaled; signing and hash verification stay non-negotiable.
2026-06-01 09:55:22 -04:00
Fimeg
2a0c800659 v0.2.2.0: enforce package state machine across all transitions, vuln dashboard
Route every current_package_state status change through one transitionStatus
path: read the observed status, validate against PackageStatusTransitions,
run a status-guarded UPDATE, record terminal history. Replaces ten raw-SQL
transition functions whose WHERE guards validated nothing and silently
no-op'd on an illegal state. ApproveUpdate, the Reject/Install/Set* family,
BulkApprove and UpdatePackageStatus now share the core; illegal moves return
a named from->to error instead of a silent miss, and concurrent callers are
caught by the guarded row count.

Migration 047 renames the terminal success state updated -> installed in
current_package_state and update_version_history, realigning both CHECK
constraints with the Go PackageStatus/HistoryStatus constants.
UpdateStats updated_updates -> installed_updates to match.

UpdateCurrentStateInTx documents its reconcile CASE as the SQL twin of
models.ReconcileFromScan so the two stay in lockstep.

Dashboard: vulnerable-package count surfaced in AttentionPanel, plus a
Vulnerable quick-filter on the Updates view.
2026-05-31 16:39:58 -04:00
Fimeg
0d908ba512 feat: zero-sudo agent, helper self-upgrade, OSV expansion, docker handler rewrite, staging UI
Agent privilege reduction:
- apt discovery unprivileged (sandbox opts like dnf)
- docker commands use group membership, no sudo
- agent self-upgrade delegated to helper via agent-self capability token
- sudoers template stripped to single systemd-run helper line

Helper (Rust):
- agent-self package type: stage, hash-verify, backup, install, chmod, restart
- TOCTOU-safe: copy-then-hash, never hash a path re-read later
- --no-block restart so helper finishes before agent SIGTERM

Server:
- mintAgentSelfToken signs agent-self tokens for manual and bulk update paths
- OSV.dev checks at discovery time (async, deduped) + startup backfill
- apt/dnf added to OSV ecosystem mapping
- docker handler rewritten against DockerQueries (proper image/container split)
- status filter server-side on aggregated packages (HAVING clause)
- dead code removed: UpdatePackage model, UpsertUpdate, ListUpdates

Frontend:
- LiveOperations -> Staging with staged-packages section
- Docker severity from data, not hardcoded
- Updates status filter delegated to server
2026-05-30 12:56:40 -04:00
Fimeg
e8afcc7994 feat: machine-bound token renewal + refresh-token rotation with reuse detection
Bind /renew to the registered machine so a stolen refresh token can't mint
tokens from another host. Rotate the refresh token on every renewal; replaying
a consumed token whose successor is also consumed revokes the family. Accept-
previous-once grace covers agent crash-before-save. Typed auth errors so the
polling loop renews on 401 and treats refresh/machine failures as terminal.
2026-05-29 10:47:48 -04:00
Fimeg
487ffa89ef feat: package-centric updates, version timeline, and registry-gap closure (v0.2.0.7)
Lands the long-dropped in-flight work plus two slices of the pinning-mirror direction.

Registry-gap closure (in-flight, was repeatedly dropped):
- Agent resolves canonical artifact hashes from its own signed repo metadata
  (dnf download + rpm header; apt-cache policy+show) — server no longer serves a
  placeholder dnf URL and says so honestly.
- Server pins the agent-reported closure and mints the capability token at the
  dependency-confirmation boundary; receipt updates package status.

Slice 1 — package detail pane:
- GET /updates/:id/fleet (cross-agent view). Detail pane gains Supply Chain card
  (pinned sha256, published/age, age-gate verdict, resolved closure) and Affected
  Agents card (per-host version delta + status, click-to-pivot).

Package-centric Updates list:
- ListAggregatedPackages rollup (GET /packages): one row per package across the
  fleet — agent/version counts, max severity, vuln + hash-pin rollups, status
  breakdown. List view rewritten to package rows that drill into the fleet view.

Slice 2 — version timeline catalog:
- migration 043 package_versions; idempotent upsert populated at scan, enriched at
  approval (OSV posture, publish date, hash) and at closure pin (per-artifact hash).
- GET /updates/:id/versions + Version Timeline card.

UI: description overflow fix, shared table density px-6->px-4, status label cleanup.
Version: 0.2.0.7 across versions.go, docker-compose, Makefile (Makefile was stale at
0.2.0.3/0.2.0).
2026-05-28 20:25:59 -04:00
Fimeg
f0f18d7320 refactor(agent): scanner orchestrator cleanup + kernel-enforcement wiring + hash-registry follow-ups
Scanner refactor:
- Move Name() onto each scanner; drop scanner_wrappers.go, registry.go,
  scanner_types.go and the duplicate scanner/docker.go (folded into
  orchestrator/docker_scanner.go)
- Add Name() to DNFScanner (was missing — broke orchestrator.Scanner)
- dnf_test.go coverage

Kernel enforcement (Tier 2 scaffold, wired into loop):
- agent/internal/kernel: enforcer, ebpf consumer, windows WDAC stub
- config.KernelEnforcementConfig + defaults/merge, wired in loop.go

Hash registry (Layer 1) follow-ups:
- client GetExpectedHash uses /api/v1/updates/verify-hash
- UpdateHandler takes config; computeAndStorePackageHash uses PublicURL

Server:
- Migration 041: update version_history status constraint
- docker reject path writes "ignored" (matches new constraint)
- queries/filter.go shared filter helper
- updates UI enhancements
2026-05-28 13:31:51 -04:00
Fimeg
1a4425b09f Layer 1: Hash Registry — verify package SHA256 before installation
Server:
- ApproveUpdate() now calls computeAndStorePackageHash() to download artifact,
  compute SHA256, and store in DB
- GET /dashboard/updates/verify-hash endpoint for agents to fetch hashes

Database:
- Migration 040: added expected_sha256 VARCHAR(64) to current_package_state table

Agent:
- HandleInstallUpdates() fetches expected hash from server before install
- DNFInstaller.VerifyHash() downloads and verifies package hash
- APT/Docker/Winget/WindowsUpdate: hash verification stubs (fail-open)
- LRU cache (100 entries) to reduce server load

Security:
- Hash verification happens BEFORE package manager install
- Mismatch blocks installation with error logged
- Fail-open: hash fetch failure doesn't block, but verification failure does
2026-05-26 13:25:54 -04:00
Fimeg
2d7911f2d0 chore: bump to v0.2.0.5 + agent↔tracked_software bindings 2026-05-25 19:52:48 -04:00
Fimeg
76ef5ae0e3 feat: auto-heartbeat at dispatch, event narratives, policy table (migration 038)
Three structural pieces that only make sense as a unit: the dispatch
chokepoint queries the policy table, and the event renderer is consumed by
the same handlers the policy gates guard.

B. Auto-heartbeat at the dispatch chokepoint (agents.go, models/command.go)
  - models.RequiresRapidPolling(commandType): central classification (no
    per-handler opt-in for rapid-polling commands).
  - signAndCreateCommand auto-queues enable_heartbeat (Source=system)
    ahead of any rapid-polling command, unless the agent is already in an
    active heartbeat window.
  - TimeoutService.reconcileAgentUpdates: effectiveUpdateTimeout() reads
    operational.update_stuck_minutes live (no restart needed).

C. Event renderer (services/event_renderer.go NEW)
  - RenderSystemEvent / RenderUpdateLog: single source of operator-facing
    verbiage.
  - Narrative field on SystemEvent / UpdateLog / UnifiedHistoryItem
    (JSON-only, not persisted). Populated in agent_events.go GetAgentEvents
    and updates.go GetAllLogs.
  - ChatTimeline.tsx consults narrative only at the prior fallback line —
    real stdout / package extraction branches untouched.

D. Policy table (migration 038 + security_settings_service helpers)
  - policy.allow_dry_runs (default true): updates.go::InstallUpdate
    returns 403 when false.
  - policy.require_nonce (default true): agent_updates.go::UpdateAgent
    skips nonce validation when false (logged at INFO).
  - policy.auto_heartbeat_enabled (default true): agents.go gates the
    auto-heartbeat side-effect.
  - operational.update_stuck_minutes (default 5): TimeoutService reads
    live for reconcile threshold.
  - GetPolicyBool / GetOperationalInt on SecuritySettingsService.

Forward-only (no policy.allow_downgrade) is ETHOS §2 doctrine, not a knob.
2026-05-25 14:12:23 -04:00
Fimeg
86da7471ec feat: install/sync/RL hardening + upstream version sync subsystem
Agent + install:
- linux installer adds redflag-agent to docker group (idempotent;
  unblocks container scanner detection)
- install.sh / linux.sh.tmpl / windows.ps1.tmpl: detect existing
  refresh_token and skip --register (Flow 2: upgrade in place)
- agent_update.go: remove dead post-restart watchdog; add
  CleanupPostUpdateBackup() called after first successful check-in

Server + token model:
- 409 machine-already-registered now returns existing_agent_id,
  hostname, last_seen, remediation guidance
- RevokeAgent admin handler (invalidates refresh_tokens only)
- GetAgentsBoundToToken query + no-cascade invariant test
- Two-axis revocation locked in: registration_token revoke does NOT
  cascade to agent refresh_tokens

Supply chain (Shai-Hulud defense feature 1):
- services/package_age.go: npm + PyPI registry probes,
  EvaluatePackageAgeGate decision matrix (warn/block/off x
  above/below/unknown)
- ApproveUpdate / ApproveUpdates wired to age gate; stores
  package_published_at + supply_chain_age_check in metadata
- security_settings_service: supply_chain category defaults
  (min_package_age_hours=24, gate_enforcement=warn)

Rate limiting UI rewrite:
- Frontend was expecting per-endpoint configs + usage/summary
  routes that don't exist; backend has 6 named categories
- Rewrote RateLimiting.tsx, useRateLimits.ts, api.ts, types,
  Settings overview card to the real shape (Requests + Window in
  seconds + Enabled per category)

Agent Management UI:
- Replaced auto-pick-first-token with explicit dropdown showing
  prefix, label, seats_used/max_seats, expiry
- One-liner panel only renders when a token is selected; no more
  YOUR_REGISTRATION_TOKEN placeholder in copyable command
- Zero-token state surfaces "Generate Registration Token" CTA

Upstream version sync (new subsystem):
- Migration 035: tracked_software + upstream_drift_events
- ReleaseSource interface + Registry + Repology adapter +
  endoflife.date adapter
- Periodic Syncer goroutine with on-demand SyncOne; classifies
  drift severity (minor/major/eol); appends drift events
- /admin/upstream CRUD + drift + sync-now routes
- Dashboard "Stack Drift" panel (count + worst-3 + EOL flag)

Migration 035 runs idempotently on next startup.
2026-05-23 15:12:20 -04:00
Fimeg
7abe331bf8 command-lifecycle v2: add received state, disk-persisted dedup, authenticated download, timeout reconciler
Migration 033 adds the 'received' status to agent_commands so the server can
distinguish "agent confirmed receipt" from "sent but may be lost in flight."
Stuck-command re-issuance now excludes received commands — the TimeoutService
handles the longer timeout for those (default 30m) vs the per-poll re-issuer
(sent/pending at 5m).

The agent side: disk-persists executed command IDs to survive restart (closes
the in-memory-only dedup gap), reports received_command_ids on each check-in so
the server transitions sent→received before issuing new work, and authenticates
binary downloads with JWT+X-Machine-ID (was unauthenticated http.Get — would
401 in production).

TimeoutService extended with reconcileAgentUpdates: clears is_updating when
current_version matches updating_to_version (success), or after a 15m threshold
(timeout, with system_event) so the dashboard never shows "updating" forever.
isVersionUpgrade replaced with utils.IsNewerVersion (no panic on 2-part
versions, no false-reject on 4-part).

MarkCommand* failures elevated from [WARNING] to [ERROR] + should_retry
response hint so agents know to re-deliver results (silent drops were ETHOS #1
violations).

Fixes: build broken on public since eac8a012 (command.go accidentally emptied).
2026-05-22 08:45:16 -04:00
Fimeg
eac8a012bf audit: add ETHOS time handling and updated_at NULL issues to tracking 2026-05-21 15:57:27 -04:00
Fimeg
c8b0ee1040 refactor: rename aggregator-agent to agent for public release 2026-05-21 13:19:18 -04:00