Files
pulse/internal/api
rcourtman 0abe518219 Refresh the resource store at most once per 2s of agent reports (#2242)
Every accepted agent report refreshed the canonical resource store, and
every refresh covers the whole estate, so refresh work grew with agent
count × estate size (#2199). #2235 made each pass linear and cheaper.
This PR bounds how many passes reports can trigger.

**Behaviour.** Accepted host, Docker and Kubernetes reports, including
rejected-report liveness, refresh through
`refreshUnifiedResourceStoreAfterAgentReport`:

- The first report after a quiet window is published before its handler
returns, the same as today.
- Reports within 2 s of the previous refresh fold into one trailing
refresh at the window's end. It reads the latest state and broadcasts it
to websocket clients.
- 2 s matches `readPathRegistryFreshness`, the staleness the read paths
already accept.
- Agent removal and host-agent evaluation still refresh immediately, so
removed inventory retires at once.
- `Monitor.Stop` cancels a pending trailing refresh and waits for a
running one before closing stores.
- Monitors built without `New` keep a zero window and refresh on every
report.

**Cost bound.** At 2,080 resources one refresh costs about 82 ms (after
#2235). 200 agents reporting every 30 s drive 6.7 refreshes/s, roughly
55% of a core; coalesced, reports trigger at most one refresh per 2 s,
about 4%, however many agents report.

**Contract change.** The agent-lifecycle contract promised that an
accepted report is visible when its handler returns. It is now visible
within the 2 s window. The founder approved that trade on 24 September
2026, and the contract records it.

**Tests**

- `TestAgentReportRefreshFoldsReportsWithinWindow`: the leading report
is published synchronously; a burst inside the window is deferred; one
trailing refresh publishes the burst's latest value; after a quiet
window the next report is synchronous again. It fails when the throttle
is removed.
- `TestStopCancelsPendingAgentReportRefresh`: fails if stop does not
cancel the pending refresh.
- The issue-1753 end-to-end test posts two reports back to back, so it
now waits for the published rows instead of reading them at once.

**Verification on pulse-dev**

- `internal/monitoring` runs in 112 s against 107 s on #2235's head. No
test is slower by more than 1.2 s.
- `internal/unifiedresources`, `internal/websocket` and
`internal/api/...` pass. `TestServerInfoEndpointReportsDevelopment`
fails in that source export with and without this change, because the
export has no git checkout.

Refs #2199
2026-09-24 13:43:36 +01:00
..
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-08-31 19:47:01 +01:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-07-20 07:50:22 +01:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-04-26 17:26:16 +01:00
2026-01-22 00:30:15 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-03-18 16:06:30 +00:00
2026-04-11 14:51:10 +01:00
2026-03-18 16:06:30 +00:00
2026-09-01 01:02:17 +01:00
2026-03-18 16:06:30 +00:00

Internal API Package

This directory contains the API server implementation for Pulse.

Important Note About frontend-modern/

The frontend-modern/ subdirectory that appears here is:

  • AUTO-GENERATED during builds
  • NOT the source code - just a build artifact
  • IN .gitignore - never committed
  • REQUIRED BY GO - The embed directive needs it here

Frontend Development Location

👉 Edit frontend files at: ${PULSE_REPOS_DIR}/pulse/frontend-modern/src/

Why This Structure?

Go's //go:embed directive has limitations:

  1. Cannot use ../ paths to access parent directories
  2. Cannot follow symbolic links
  3. Must embed files within the Go module

This is a known Go limitation and our structure works around it.