Task · OPN-0122

Firmware collector: info endpoint outgrows the client timeout and status polls uncached while last_check is empty

Description

Since 2026-10-07 api/core/firmware/info on the production box takes 27-35s to answer (measured directly; response shape unchanged: product_id, product_version, plugin[], package[] ~950 rows). The client’s default per-request timeout is 15s with 3 retries, so the firmware collector has been at 0% success on every cold-tier poll since 2026-10-12, plugin inventory metrics are absent and OPNsenseCollectorDegraded{collector=firmware} is firing. Separately, after the 26.7.6 upgrade the box has not run an update check, so api/core/firmware/status returns an empty last_check; firmwareStatusCacheable refuses to cache that body (retired issue #724 rule), and the security-posture config snapshot (opnsense/security_posture.go via internal/logship/configsnapshot) now calls the endpoint live about every 10s instead of roughly twice a day. Both are in opnsense/firmware.go and internal/options/cache.go.

Acceptance Criteria

Definition of Done

Implementation Plan

Implement the firmware request/cache seam; fail reproduction tests before fixes; return staged candidate after local gate for root security review, CodeRabbit, root publish and exact-SHA CI/live proof.

Implementation Notes

loop10: source candidate passed public-client fail-first/pass proof and exact full gate; security review PASS and completed CodeRabbit with zero findings. Firmware info now gets a request-local 60s floor, global remains 15s and caller context wins. Empty/omitted/whitespace last_check uses min(configured TTL,15m), four requests/hour for successful sequential resident-cache use at defaults; checked status retains configured TTL, zero disables. Newly completed checks can lag 15m. TestClient_CacheRefusesFirmwareStatusWithoutLastCheck intentionally updated to bounded-caching behavior; unrelated interim tests preserved. One implementation attempt, one uncharged lint-lock infrastructure retry. Source will land under root publication; AC3 remains In Progress until exact-build rollout and two cold-tier polls plus status request bound are observed.

loop10: 1 implementation cycle, completed. Exact firmware source SHA passed local/composed gate, guarded review, CodeRabbit and CI; exact build observed within the rollout bound. Stable later prerelease build maps through the annotated Git tag to an unchanged firmware/cache descendant; two consecutive cold-tier success polls and status request increase 3.064747 per hour meet the unchanged four/hour bound. Watcher identity false-negatives (stale old version series and semantic version labels) were resolved from actual sample timestamps/tag ancestry, never by relaxing acceptance.

Final Summary

Per-endpoint 60s firmware-info timeout preserves 15s global timeout and caller deadlines; unchecked status cache min(configured TTL,15m) bounds successful sequential requests while retaining checked TTL. Real slow-server and fake-clock fail/pass tests, full gates, guarded review, CodeRabbit, exact CI and live cold-poll/request-rate proof verified.

View the source file on GitHub