Commit Graph

60 Commits

Author SHA1 Message Date
pulse-triage[bot] b4ada48e1f Keep breach-date fixture errors in their originating subtest
Pass the current test to status decoding as well as date assertions, and describe the external-test-package boundary precisely. The independent receiver oracle and public data behaviour are unchanged.

Change-source: pulse-maintainer
2026-10-09 21:54:52 +01:00
pulse-triage[bot] 10ad9b22bf Keep the breach-date websocket control self-contained
The parent comparison executed seven expected date failures but could not
compile the websocket control: its receiver helper belongs to package
websocket_test and is not visible from package websocket. Keep the independent
RFC 7396 oracle beside this control and attribute each date assertion to its
actual subtest.

Change-source: pulse-maintainer
2026-10-09 21:54:50 +01:00
pulse-triage[bot] 5709c6073c Preserve held threshold alerts’ breach dates in live state
Date the evaluated breach through metricStatus rather than publishing poll-varying lifecycle LastSeen. Holds and recovery retain the saved date; unknown dates stay absent and restart rebuilds the volatile status from the existing checkpoint.

Pin exact-trigger and renewed breaches, both restart sources, unchanged recovery-mirror writes, concrete state JSON and keyed websocket withdrawal. Preserve thresholds, notification timing and existing consumer compatibility.

Refs: #2626
Change-source: pulse-maintainer
2026-10-09 21:44:37 +01:00
pulse-triage[bot] 1c1ba27fe6 Preserve both reviewed active-alert message and value assertions
The two independently reviewed fixture repairs overlap. Keep the existing message-arrival/change oracle alongside Core\x27s numeric-value, mixed insertion/removal, target, tenant and no-frame checks rather than dropping either guarantee during integration.

Contract-Neutral: Integration-only test assertion preservation; runtime and wire contracts are unchanged.
Change-source: pulse-maintainer
2026-10-06 20:26:12 +01:00
pulse-triage[bot] 4b849cff83 Keep dashboard equivalence on canonical active alerts
The connected-dashboard fixture still referenced the removed ResourceFrontend.Alerts and ResourceAlertFrontend model, preventing the websocket tests from compiling. Exercise resource-scoped alerts through StateFrontend.ActiveAlerts instead, including independent connected insertion, changed-value and removal oracles, while retaining exact-number, tenant, baseline and unchanged-frame checks.

Contract-Neutral: Test fixture repair for existing active-alert ownership; websocket runtime and wire contracts are unchanged.
Change-source: pulse-maintainer
2026-10-06 19:11:07 +01:00
pulse-triage[bot] 390fbb701e Keep Linux proc and resource-usage probes out of other platform tests
The opt-in residency fixture reads procfs and Linux rusage. Restrict that test file to Linux; its compiled Linux body and every production file are unchanged.

Change-source: pulse-maintainer
2026-10-06 12:51:18 +01:00
pulse-triage[bot] ccce221fe7 Keep connected cost fixtures compiling and reject missing live frames
Remove an unused test import exposed by source-fciz7jhb and require the quiet-aware cost probe to retain all clients and actually queue changed readings.

Change-source: pulse-maintainer
2026-10-06 12:14:07 +01:00
pulse-triage[bot] cb61f75419 Preserve canonical Unicode and duplicate-key encoding in raw patches
Normalise newly added fields and identity anchors through the exact-number decoder/encoder; require byte-for-byte parent-oracle equivalence as well as values, including lone surrogate and invalid UTF-8 controls.

Contract-Neutral: Preserve the existing exact emitted JSON patch representation for additions and identity anchors.
Change-source: pulse-maintainer
2026-10-06 12:13:08 +01:00
pulse-triage[bot] b80edf03fc Measure connected dashboard CPU and residency at reported scale
Keep exact 1236-resource workload assertions and opt-in source-native CPU/allocation/after-GC heap/RSS observations for cold, quiet, all-row heartbeat and churn scenarios. No installed relief is inferred from these measurements.

Change-source: pulse-maintainer
2026-10-06 12:12:01 +01:00
pulse-triage[bot] 4f1dc52bc3 Keep the root identity anchor in the first decoded raw field map
Avoid a second full current-object decoding while retaining the exact parent merge-patch identity rule for object/object and replacement transitions.

Contract-Neutral: Same merge-patch values and identity anchoring; removes a redundant current-object decode only.
Change-source: pulse-maintainer
2026-10-06 12:11:02 +01:00
pulse-triage[bot] 9388cc2c73 Compare connected dashboard lifecycle with the exact parent delta oracle
Exercise advancing heartbeat and exact counters, metadata/catalog/health/alert changes, ownership, re-enrolment, removal, quiet updates and tenant isolation through production dispatch. The reference remains the supplied parent implementation.

Change-source: pulse-maintainer
2026-10-06 12:09:42 +01:00
pulse-triage[bot] ee8b88df69 Reduce repeated work in connected dashboard broadcasts
Precompute canonical registry sort keys once per row, compare immutable JSON
object fields before decoding unchanged nested metadata, and reuse one exact
baseline-to-current frame within a serial dispatch. Each client's hydration,
frame limit, queue acceptance and tenant selection remain authoritative.
No cross-broadcast conversion or content-hash cache is introduced.

Reported connected-path evidence: Pulse #2199, AndyProsser comment 6013106749.

Contract-Neutral: Same canonical ordering, exact-number JSON merge semantics and per-client/tenant state protocol; only transient computation and allocations change.
Change-source: pulse-maintainer
2026-10-06 12:08:50 +01:00
pulse-triage[bot] 666912d732 Preserve numeric counter precision in dashboard deltas
Compare and emit exact JSON number tokens instead of converting keyed resource, infrastructure and alert payloads through float64. Adjacent large counters must not disappear, and changed nested objects and arrays must not be rounded. Retain single-value parsing, merge removals, identity binding, per-client baselines and tenant isolation. Keep an exact parent helper for same-executable synthetic cost comparisons.

Change-source: pulse-maintainer
Contract-Neutral: Restore authoritative JSON numeric values; existing delta schema, keying, tenant isolation and queue lineage are unchanged.
2026-10-06 09:32:29 +01:00
pulse-triage[bot] 199a8afc95 Exercise exact numeric dashboard deltas
Bind large signed and unsigned counters to actual per-client frames, tenant isolation, rejected queues and unchanged baselines. Keep invalid and trailing JSON refusal explicit. These controls expose the existing float64 merge-patch loss before its repair.

Change-source: pulse-maintainer
2026-10-06 09:29:28 +01:00
pulse-triage[bot] 9a05a5d477 Keep connected metadata controls independent of tag ordering
Canonical ordering is already exercised by the repeated map and quiet rebuild controls. Let the connected replace/add/remove/clear/restore and tenant-isolation test independently establish that real metadata still propagates on both parent and repaired adapters, rather than making this healthy control fail solely for the known parent ordering defect.

Change-source: pulse-maintainer
2026-10-03 16:09:13 +01:00
pulse-triage[bot] 4e57511d1e Stabilise label-derived tags without hiding metadata changes
Docker and Kubernetes label maps became ordered tag arrays in random map iteration order, forcing unchanged resources into connected-dashboard deltas (#2441, found while profiling #2199). Sort only the deduplicated label-derived list. Preserve existing label values, source maps, first-choice image references and identity ordering. Exercise all seven affected Docker adapters and twenty-six Kubernetes kinds through registry rebuilds, monitor projection, per-client queues and receiver replay, including real replace/add/remove/clear/restore changes, tenant isolation and a thousand-pod heartbeat.

Contract-Neutral: Deterministic ordering of existing label-derived tag sets only; no public JSON shape, canonical ownership or ordered identity-list change.
Change-source: pulse-maintainer
2026-10-03 16:05:05 +01:00
pulse-triage[bot] 4b01e25cdf Remove redundant connected-dashboard projection and snapshot copies
List the continuity-aware registry once, decorate one owned host projection, and sort final frontend rows rather than a second estate-sized conversion array. Encode concrete frontend resources directly into immutable snapshot buffers, decoding every authoritative ID from those bytes; leave generic marshalers and all other fields on the existing path. Keep live freshness, alert and lifecycle semantics without a cache.

Change-source: pulse-maintainer
2026-10-01 11:32:30 +01:00
pulse-triage[bot] db9e089691 Measure the complete connected-dashboard broadcast path
Pin a reproducible 1000-resource completed-ingest fixture with zero, one and four viewers before changing projection or encoding. Measure real monitor registry reads, projection, snapshot, per-client delta and queues, excluding persistence, timers and network transport.

Change-source: pulse-maintainer
2026-10-01 11:25:13 +01:00
pulse-triage[bot] 901274ee92 Reduce snapshot and clone allocations without trusting ID hints
Batch WebSocket identity decoding from the encoded arrays and retain decoder-owned entry buffers. Keep canonical metadata refresh on every resource clone while replacing membership maps with small scope slices and a single sensitivity-tag scan. Preserve delta identity, scope ordering, policy precedence and clone isolation with reference/fuzz and transition tests.

Adapt the remaining performance work proposed in Pulse PR #2342 for issue #2199. The contributor's first-ID check cannot verify a stale tail; this candidate uses standard JSON decoding for every encoded identity instead. Keep the imported contributor history and branch unchanged.

Co-authored-by: Andy Prosser <andy.prosser@icloud.com>
Based-on: https://github.com/rcourtman/Pulse/pull/2342
Original-Commit: 0cbfa43186
Change-source: pulse-maintainer
2026-09-30 13:21:56 +01:00
rcourtman 0bf66d7c72 Space websocket state broadcasts at least 2s apart per audience
Every accepted agent report sends a state invalidation, and each state
broadcast rebuilds and diffs the full frontend state, about 130 ms at
2,080 synthetic resources. The hub coalesced invalidations with a 100 ms
debounce that restarted on every signal, so reports spaced more than
100 ms apart each produced a broadcast, while signals arriving closer
than the window could postpone broadcasts until they stopped (#2199).
With 200 agents reporting every 30 seconds, that was roughly 3.4
broadcasts a second, about half a core whenever a browser was open.

Coalescing is now a throttle. The first invalidation after a quiet
period still broadcasts after the 100 ms window. Later invalidations for
the same audience (all clients, or one tenant) replace the pending
request without rescheduling it, and broadcast no sooner than 2 seconds
after that audience's previous broadcast. That caps broadcast work near
7% of a core at the same scale. Two seconds matches the report-driven
store refresh interval, so faster broadcasts mostly re-sent the same
resources.

Refs #2199
2026-09-24 11:21:20 +01:00
pulse-triage[bot] 59deb570d5 fix(perf): compare registry generations without deep clones
Keep canonical change classification while avoiding two full List clones on accepted ingest. Document the registry and monitoring boundaries, add exact verification tests, and retain 1,000-resource benchmarks for #2199.

Change-source: pulse-maintainer
2026-09-24 07:44:36 +01:00
rcourtman 0fece386c4 Ship active alerts as per-client keyed deltas
Second increment of the resource-payload-static-metadata gap: the full
activeAlerts array (~37-75KB at mock-estate churn) re-shipped whenever
any single alert changed. Alerts now ride the generalized keyed delta
transport: the per-field keying and transition handling in the state
delta engine is a declarative keyedDeltaFields list shared with the
connected-infrastructure projection, and activeAlertsDelta carries
id-keyed merge patches, removed ids, and order. Measured on the mock
estate the per-tick alert payload drops to 184-1981 bytes.

One boundary held deliberately fixed: alert application timing does not
change. The client applies alert deltas to an isolated baseline and
feeds the exact same applyActiveAlerts path immediately on arrival,
input-active or not — alert lifecycle truth never waits for the
operator-input idle gate (alerts subsystem boundary, documented in
6ecdc81d1), and the pending-acknowledgment safeguards see byte-
equivalent records. Alerts handed to the store are cloned so it never
adopts baseline-owned objects. Deltas without a baseline are ignored
and heal on the next full payload; unkeyable entries fall back to
whole-field diffing.

Remaining gap scope unchanged: snapshot-side static metadata
(canonicalIdentity alias history ~609KB per snapshot, and the partially
regressed aiSafeSummary/policy omissions) stays open under
resource-payload-static-metadata.
2026-08-26 18:23:50 +01:00
rcourtman f3d3434652 Ship the infrastructure projection as per-client keyed deltas
First increment of the resource-payload-static-metadata gap: the
connected-infrastructure reporting projection was re-shipped whole
(~55-60KB at 50-node scale) on every realtime broadcast even though
measurement showed the only field that ever changes tick-to-tick is
lastSeen, and the client deep-reconciled all 69 rich items per tick —
the dominant reactive cost inside every websocket message after the
resource merge itself.

The projection now rides the same per-client keyed delta engine as
resources. buildClientStateSnapshot keys it by id beside the resource
map, and buildClientStateDelta emits connectedInfrastructureDelta with
id-keyed JSON merge patches, removed ids, and order through one shared
keyed-array builder. Measured on the mock estate the per-tick payload
drops from ~55-60KB to 0.7-4KB of timestamp patches. Entries that
cannot key by id fall the whole field back to plain whole-payload
diffing, and keyed/unkeyed baseline transitions re-ship the full array
once, so the keyed path can never corrupt the projection.

The client mirrors the resource baseline pattern: full payloads
establish an isolated raw baseline, deltas apply as merge patches
through the now-generic keyed apply helper, and the store sync hands
reconcile reference-stable objects for untouched items so a tick
reconciles only what changed instead of deep-walking the projection.
Deltas without a baseline are ignored and heal on the next full
payload; the operator-input deferral gate carries the pending changed
ids across a gesture and flushes them in the same coalesced batch.

The remaining gap scope (snapshot-side static metadata such as
canonicalIdentity alias history, ~609KB per snapshot) stays open under
resource-payload-static-metadata.
2026-08-26 18:09:34 +01:00
courtmanr@gmail.com e21a3c4434 Classify normal WebSocket shutdowns as informational 2026-08-11 16:38:21 +01:00
rcourtman 541c9be7fd fix(websocket): keep oversized recovery baseline-free 2026-08-08 01:35:49 +01:00
rcourtman 962d297803 fix(websocket): send resource deltas after initial state
Refs #1601
Refs #1665
Refs #1497
2026-08-06 00:48:37 +01:00
rcourtman 58864f5ecc Accept any inbound websocket frame as proof of client liveness
The read deadline was refreshed only by protocol pong frames, so a client
whose ping/pong control frames were delayed more than 6 seconds or eaten
by a middlebox (Cloudflare edge, AV proxies) was killed every 60 seconds
even while its 30-second JSON heartbeats were arriving. Demo logs show
the signature clearly: sessions dying at exactly 60s/114s/168s, i.e.
zero, one, or two pongs before execution, while the user watched the
badge cycle through Reconnecting on an otherwise healthy connection.

Refresh the read deadline on every successful read, widen it to 90s so a
background tab throttled to one heartbeat per minute still survives, and
ping every 30s so pong-only clients tolerate two lost round trips.

Contract-Neutral: websocket keepalive hardening: read-deadline/ping cadence only, no payload or contract delta
2026-08-05 12:24:07 +01:00
courtmanr@gmail.com fe33d23210 Document websocket lifecycle synchronization 2026-07-26 22:02:22 +01:00
courtmanr@gmail.com 27ba37982e Synchronize websocket client lifecycle signal 2026-07-26 21:56:52 +01:00
rcourtman 87fe506c0a Bound state refresh and recovery ingest ownership 2026-07-23 22:24:52 +01:00
rcourtman 042e7ef966 Harden remaining CodeQL security boundaries 2026-07-09 19:46:40 +01:00
rcourtman dcf541784d Bound server state broadcast memory
Refs #1442
2026-07-02 23:27:49 +01:00
rcourtman c47560ad69 Fix websocket state broadcast retention
Refs #1442
2026-06-24 11:16:27 +01:00
rcourtman faefe6edc8 Remove 198 unreachable Go functions
Dead-code sweep. Functions flagged unreachable by golang.org/x/tools/cmd/deadcode
and confirmed unused across pulse, pulse-enterprise, pulse-pro and pulse-mobile by
adversarial cross-repo verification. Cross-module reachability was checked
explicitly (only pkg/ exported symbols are importable by other modules; internal/
packages and _test.go files are not). go build, go vet and test-compile all pass.
2026-06-03 12:29:37 +01:00
rcourtman 0b57527b29 Dedup concurrent /api/state and /api/diagnostics with singleflight
Under load, 5x concurrent /api/state degraded from 276ms (single) to ~4s
each (linear), because every caller serialized on the monitor lock to
rebuild and JSON-encode the full 1.6MB state. /api/diagnostics had the
same dogpile shape on cache miss, even though its 45s TTL cache was
working as designed.

Wrap both handlers in a per-tenant singleflight.Group so concurrent
callers share the work: 20x concurrent /api/state now completes in 358ms
wall (~14-50x improvement). Diagnostics warm-cache responses are now
sub-3ms; cold compute coalesces.

Also drop websocket Upgrader buffers from 4MB read/write to 64KB. gorilla
streams larger payloads across the buffer transparently, so the 4MB
allocation per connection was overhead that scaled badly with concurrent
clients (100 clients * 8MB = 800MB just in buffers).

Contract-neutral: no endpoint, response body, header, or wire-format
change.
2026-05-25 20:07:02 +01:00
rcourtman 53838d8f93 Fix RC3 backend release blockers 2026-05-01 21:36:28 +01:00
rcourtman ccb2edc3b8 Require explicit websocket origin continuity 2026-04-22 04:46:13 +01:00
rcourtman 2d8385c1d8 Fix same-host websocket proxy origin checks 2026-04-13 12:04:29 +01:00
rcourtman 778a2577b6 feat: Pulse v6 release 2026-03-18 16:06:30 +00:00
rcourtman 499ab812e3 Fix post-release regressions and lock v5 to single-tenant runtime 2026-03-05 23:46:35 +00:00
rcourtman a9ed380718 fix(websocket): respect X-Forwarded headers in same-origin check
- Use X-Forwarded-Proto/X-Forwarded-Scheme for scheme detection
- Use X-Forwarded-Host for host matching behind reverse proxies
- Update tests with remoteAddr for CSWSH protection validation
2026-02-03 21:45:39 +00:00
rcourtman c295ee277f security: add scope checks to AI endpoints and mitigate CSWSH
- AI Intelligence endpoints (/api/ai/intelligence/*, /api/ai/forecast/*,
  /api/ai/unified/findings, etc.) now require ai:execute scope to prevent
  low-privilege tokens from reading sensitive intelligence data

- AI Knowledge endpoints (/api/ai/knowledge/*) now require ai:chat scope
  to prevent arbitrary guest data access across the fleet

- AI Debug Context (/api/ai/debug/context) now requires settings:read scope
  to prevent system prompt and infrastructure details leakage

- WebSocket origin check now validates peer IP is private when allowing
  private network origins, mitigating CSWSH attacks where a malicious page
  on the same LAN tries to hijack connections using victim's session cookie
2026-02-03 19:40:46 +00:00
rcourtman b2639ed5a5 Fix security vulnerabilities and critical bugs
- Fix WebSocket CORS bypass by strictly verifying origin
- Fix OIDC refresh token persistence by encrypting at rest
- Fix grouped webhook data mutation by cloning alerts
- Fix host agent uninstall authorization and config fetch logic
- Fix notification queue recovery for stuck sending items
- Fix ignored update history limit parameter
- Fix ineffective break statement in WebSocket write pump
2026-02-03 17:16:27 +00:00
rcourtman aeca5e39fa Fix multi-tenant persistence and backend stability
- Initialize Alert and Notification managers with tenant-specific data directories

- Add panic recovery to WebSocket safeSend for stability

- Record host metrics to history for sparkline support
2026-02-03 16:24:42 +00:00
rcourtman 19a67dd4f3 Update core infrastructure components
Config:
- AI configuration improvements
- API tokens handling
- Persistence layer updates

Host Agent:
- Command execution improvements
- Better test coverage

Infrastructure Discovery:
- Service improvements
- Enhanced test coverage

Models:
- State snapshot updates
- Model improvements

Monitoring:
- Polling improvements
- Guest config handling
- Storage config support

WebSocket:
- Hub tenant test updates

Service Discovery:
- New service discovery module
2026-01-28 16:52:35 +00:00
rcourtman 62e8d609de feat: add WebSocket multi-tenant isolation
- Enhance WebSocket hub with tenant awareness
- Add tenant isolation for real-time updates
- Add hub tenant isolation tests
2026-01-24 22:43:50 +00:00
rcourtman c4ca169e2b feat: add multi-tenant isolation foundation (disabled by default)
Implements multi-tenant infrastructure for organization-based data isolation.
Feature is gated behind PULSE_MULTI_TENANT_ENABLED env var and requires
Enterprise license - no impact on existing users.

Core components:
- TenantMiddleware: extracts org ID, validates access, 501/402 responses
- AuthorizationChecker: token/user access validation for organizations
- MultiTenantChecker: WebSocket upgrade gating with license check
- Per-tenant audit logging via LogAuditEventForTenant
- Organization model with membership support

Gating behavior:
- Feature flag disabled: 501 Not Implemented for non-default orgs
- Flag enabled, no license: 402 Payment Required
- Default org always works regardless of flag/license

Documentation added: docs/MULTI_TENANT.md
2026-01-23 21:42:27 +00:00
rcourtman 2e0da42a81 chore: reliability and maintenance improvements
Host agent:
- Add SHA256 checksum verification for downloaded binaries
- Verify checksum file matches expected bundle filename

WebSocket:
- Add write failure tracking with graceful disconnection
- Increase write deadline to 30s for large state payloads
- Better handling for slow clients (Raspberry Pi, slow networks)

Monitoring:
- Remove unused temperature proxy imports
- Add monitor polling improvements
- Expand test coverage

Other:
- Update package.json dependencies
- Fix generate-release-notes.sh path handling
- Minor reporting engine cleanup
2026-01-22 00:45:04 +00:00
rcourtman a6a8efaa65 test: Add comprehensive test coverage across packages
New test files with expanded coverage:

API tests:
- ai_handler_test.go: AI handler unit tests with mocking
- agent_profiles_tools_test.go: Profile management tests
- alerts_endpoints_test.go: Alert API endpoint tests
- alerts_test.go: Updated for interface changes
- audit_handlers_test.go: Audit handler tests
- frontend_embed_test.go: Frontend embedding tests
- metadata_handlers_test.go, metadata_provider_test.go: Metadata tests
- notifications_test.go: Updated for interface changes
- profile_suggestions_test.go: Profile suggestion tests
- saml_service_test.go: SAML authentication tests
- sensor_proxy_gate_test.go: Sensor proxy tests
- updates_test.go: Updated for interface changes

Agent tests:
- dockeragent/signature_test.go: Docker agent signature tests
- hostagent/agent_metrics_test.go: Host agent metrics tests
- hostagent/commands_test.go: Command execution tests
- hostagent/network_helpers_test.go: Network helper tests
- hostagent/proxmox_setup_test.go: Updated setup tests
- kubernetesagent/*_test.go: Kubernetes agent tests

Core package tests:
- monitoring/kubernetes_agents_test.go, reload_test.go
- remoteconfig/client_test.go, signature_test.go
- sensors/collector_test.go
- updates/adapter_installsh_*_test.go: Install adapter tests
- updates/manager_*_test.go: Update manager tests
- websocket/hub_*_test.go: WebSocket hub tests

Library tests:
- pkg/audit/export_test.go: Audit export tests
- pkg/metrics/store_test.go: Metrics store tests
- pkg/proxmox/*_test.go: Proxmox client tests
- pkg/reporting/reporting_test.go: Reporting tests
- pkg/server/*_test.go: Server tests
- pkg/tlsutil/extra_test.go: TLS utility tests

Total: ~8000 lines of new test code
2026-01-19 19:26:18 +00:00
rcourtman d3116defe3 fix: Prevent panic from send on closed websocket channel
Add atomic `closed` flag to Client struct and `safeSend()` helper method
to prevent race condition when sending to client channels. The race
occurred when a client disconnected while a goroutine was trying to send
initial state - the channel could be closed between the registration
check and the actual send.

All sends to client.send now go through safeSend() which checks the
closed flag first. The flag is set atomically before closing the channel
in all code paths (unregister, dispatchToClients, broadcast, shutdown).

Related to #1048
2026-01-06 17:41:25 +00:00