Commit Graph

475 Commits

Author SHA1 Message Date
rcourtman 278673aa0f Add Go branch-coverage tests for twelve pure backend helpers
New *_branchcov0718_test.go files raise coverage of previously-uncovered pure
functions across ten packages. Covered areas include securityutil SSRF and URL
validation, truenas path and telemetry parse helpers, storagehealth SMART and
physical-disk risk assessment, vmware inventory sort keys and error classifiers,
servicediscovery token filtering and readiness, telemetry evidence-from-history,
models ToFrontend converters and frontend NormalizeCollections normalizers,
actionplanner type predicates and canonical resource-id sort, config API-token
accessors, and licensing state accessors.

Test-only, with no runtime or subsystem-contract change. Verified in a clean
worktree at HEAD with go vet and package tests green, gofmt clean, and every
named target function moved from 0 percent to covered.
2026-07-19 15:10:09 +01:00
rcourtman e4e43c3f11 feat(telemetry): attribute approved-action failures to cause buckets
The 30d window said 18 of 26 approved action attempts failed but
carried zero cause information. Every approved attempt that is not a
verified success now lands in exactly one content-free bucket:

- pre_dispatch: terminally refused before dispatch (plan drift, expiry,
  emergency stop, policy authorization)
- execution: dispatched execution failed or ended inconclusive
- unverified: execution succeeded but outcome verification was not
  confirmed (reads as success in the UI, counted as failure here)
- stuck_executing: still executing over an hour after dispatch

plus one sanitized machine reason code for the most recent failure.
Successes, failure buckets, and recent in-flight attempts partition the
attempt count, so the next window explains its own gap.

RefuseActionExecution now persists the specific refusal code
(plan_drift, action_plan_expired, ...) as the canonical execution
reason code instead of the generic pre_dispatch_refused, so audit truth
and telemetry distinguish refusal causes without message parsing.

Privacy disclosures updated in both PRIVACY.md copies; the adoption
report script surfaces the new counters.

Contract-Neutral: additive content-free telemetry counters + specific pre-dispatch refusal reason codes; privacy disclosures updated in-commit per security-privacy extension point
2026-07-17 23:39:34 +01:00
rcourtman 64fb3d198d fix(securityutil): dial every permitted resolved IP instead of pinning the first
The restricted outbound dialer resolved a hostname, validated the results,
then dialed only the first permitted IP. When localhost resolves to ::1
ahead of 127.0.0.1 and the target service binds only 127.0.0.1 (Ollama's
default), the dial failed with 'connect: connection refused' even though
curl and browsers connect fine via address-family fallback. This broke the
AI provider setup quickstart: the blessed http://localhost:11434 Ollama URL
failed Pulse's connection test on IPv6-first hosts (setup-friction class of
#847/#1003).

Try each permitted IP from the validating resolution in order until one
connects. Every candidate is still validated against the outbound policy
from the same resolution, so the DNS-rebinding pinning guarantee is
unchanged. Fixes all consumers of the shared client (Ollama provider, OIDC,
SSO, connection probes, availability poller); api-contracts wording updated
to match. Live-verified against Ollama 0.32.1 bound to 127.0.0.1 only:
TestConnection via http://localhost:11434 failed before, passes after.
2026-07-17 23:35:45 +01:00
rcourtman dadffe8a26 fix(tlsutil): discard the subprocess kill error explicitly 2026-07-17 17:26:55 +01:00
rcourtman fe09420bcb Add coverage tests for agentexec lifecycle codec and aicontracts pure helpers
The docker container lifecycle codec in agentexec had a contract test that never
exercised the codec functions at runtime, leaving decode, bind, validate and
identity helpers at zero coverage. This adds a table-driven test taking each to
near or full coverage including every validation error arm. In aicontracts,
CloneActionReference, IsNilAlertPayload and DefaultEngineConfig were uncovered,
so this covers the nil and populated arms and asserts CloneActionReference
produces an independent deep copy.

Both are new test-only files. No source changed. Verified with go test, gofmt
and go vet, with each target function confirmed to move off zero coverage.
2026-07-17 06:53:16 +01:00
rcourtman c2b8d6b9a0 Add pure branch-coverage tests for the fresh Patrol and Docker drop
Cover previously-unexecuted branches in modules that gained behavior
since the last wave. All six are newly added *_w0716_coverage_test.go
files that modify no source and no existing test.

- internal/ai/chat investigation-budget injection plus the agentic
  tool-event and string helpers
- internal/ai/qualification manifest Validate error arms and the Patrol
  docker-predicate and finding-prerequisite validators
- internal/unifiedresources adapters transforms and ContractResourceType
- internal/config AIConfig nil-receiver default branches
- pkg/aicontracts orchestrator error methods and investigation config
2026-07-16 22:27:29 +01:00
rcourtman 676117bf27 Separate Patrol evidence and model-turn budgets 2026-07-16 17:50:08 +01:00
rcourtman fb9584aae6 Add Go branch-coverage tests for alerts monitoring model-resolution ai and licensing helpers
Adds table-driven branch-coverage unit tests for previously uncovered pure
functions across internal/alerts/config, internal/ai/modelresolution,
internal/monitoring, internal/ai, internal/ai/qualification and pkg/licensing.
New test files only, with no source changes.

Covers per-subsystem alert-default normalization, configured model and provider
resolution, fleet-doctor identity and cluster-endpoint helpers, docker-state and
infrastructure-key mapping, investigation and SMART issue helpers, JSON and
autonomy normalization, and licensing feature-tier resolution. 65 TestBranchCov
functions in 7 files, all vet and gofmt clean.
2026-07-16 15:17:45 +01:00
rcourtman df4e02ef89 Add Go branch-coverage tests for models config securityutil and aicontracts helpers
Adds table-driven branch-coverage unit tests for previously uncovered pure
functions across internal/models, internal/config, pkg/securityutil and
pkg/aicontracts. New test files only, with no source changes.

Covers model collection normalizers, reflect-based metric deep-copy,
removed-host agent bookkeeping, report-schedule and entitlement-billing
normalization, effective AI control and Patrol autonomy resolution,
websocket-origin host normalization, and command-safety classifiers. 61
TestBranchCov functions in 8 files, all vet and gofmt clean.
2026-07-16 14:20:13 +01:00
rcourtman 47f2bdaed4 Add Go branch-coverage tests for pure config and helper packages
Adds table-driven branch-coverage unit tests for previously untested pure
helper functions across internal/alerts/config, internal/config,
internal/ai/safety, internal/ai/modelresolution, internal/operationreceipt,
internal/models, internal/securityutil and pkg/securityutil. New test files
only, with no source changes.

Covers alert-config normalization and validation, sensitive-path and
redaction classifiers, URL normalizers, provider model resolution,
operation-receipt decoding, credential masking, and account-to-org role
mapping. 57 TestBranchCov functions in 12 files, all vet and gofmt clean.
2026-07-16 10:19:51 +01:00
rcourtman e3382c8bcb Use authoritative Docker OOM evidence 2026-07-15 17:55:26 +01:00
rcourtman cdc109d408 Publish Patrol investigation limits live 2026-07-15 10:49:16 +01:00
rcourtman 93fff4f1d8 Add an explicit operator override for plaintext HTTP to non-local Pulse hosts
Fleets on networks numbered from nominally public IP space (issue
#1522: an AD estate on 192.20.0.0/16) cannot pass the agent's
local-network plaintext heuristic, and the only workaround was pointing
a .internal DNS alias at the server, which bypasses the same control
less visibly than a flag would. --allow-plaintext-http
(PULSE_AGENT_ALLOW_PLAINTEXT_HTTP) records process-wide consent once at
agent startup before any module validates a URL, covers every agent
transport including the websocket command channel, warns at startup
that the API token travels in cleartext, defaults closed, and is never
emitted by generated install commands or settable by the server.
2026-07-14 16:42:02 +01:00
rcourtman ae4162f8f2 Enforce installation-scoped license invalidation 2026-07-14 16:18:21 +01:00
rcourtman e1985528d9 test(coverage): agentexec codecs and licensing service branch coverage
Add purely-additive branch-coverage tests for freshly-landed, previously-
untested backend surface:
- internal/agentexec: docker container update codec (decode/validate/bind/
  digest/result-cross-validation), residual host apt codec validation arms
  (host update + storage cleanup decode/bind/result/operation-query identity),
  and NormalizeDeployMaxParallel clamping.
- pkg/licensing: service helpers (unionFeatures, safeIntFromInt64,
  remainingDaysCeil, ensureGracePeriodEnd, Set/Get Evaluator+StateMachine,
  IsValid, CurrentUnsafeForTesting, dev-mode) and activation GrantEnvelope
  ParseExpiresAt + uncapped-core-monitoring tier classification.
No source changed.
2026-07-14 14:45:55 +01:00
rcourtman 848b4d5038 Preserve customer data across plan downgrades 2026-07-14 11:47:25 +01:00
rcourtman 4aac79dc72 Add durable agent operation receipts 2026-07-12 04:16:22 +01:00
rcourtman 82c7c52727 Expose server-authored action policy provenance 2026-07-12 00:18:43 +01:00
rcourtman b7b897651b Add canonical action result truth 2026-07-11 21:27:00 +01:00
rcourtman cd7886aa44 Enforce server-owned action approval authority 2026-07-11 19:49:17 +01:00
rcourtman eb9954618a Add governed storage pressure cleanup 2026-07-11 10:11:35 +01:00
rcourtman 500d3be2a7 Align Pro feature catalog names with the new Patrol vocabulary
The public pricing surface and in-app plan-selling copy now say
"Patrol investigates issues and explains the root cause" and
"Patrol applies safe fixes and verifies the result". Rename the
licensing catalog DisplayName/ComparisonName pairs to the title-cased
forms, align the ai_autofix UpgradeReason verbs (apply/verify the
result), regenerate the frontend catalog, and update the Go, vitest,
and Playwright pins plus README and PULSE_PRO docs.
2026-07-11 09:33:47 +01:00
rcourtman 1312da3acb Add governed host update autonomy 2026-07-11 01:25:14 +01:00
rcourtman 69b2028513 Add policy-scoped Patrol autonomy 2026-07-11 00:37:34 +01:00
rcourtman 5bbfce956d Complete Patrol action lifecycle continuity 2026-07-10 21:22:06 +01:00
rcourtman a1ba51630d Commit B (pulse): typed lifecycle is the only Patrol route; delete the command side doors
OrchestratorChatService is replaced, not extended: it exposes only
ExecuteInvestigationStream (returning a structured
OrchestratorInvestigationResult) and ListInvestigationTools. The generic
ExecuteStream, SetAutonomousMode, ListAvailableTools, and the
AutonomousMode request field are gone, along with
OrchestratorCommandExecutor, OrchestratorApprovalStore, and the
autonomy/fix-verifier/license dependency interfaces. OrchestratorDeps
now carries a REQUIRED ActionBroker and no command/autonomy deps.

The pulse investigation adapter is rewritten to drive
ExecuteInvestigationStream, injecting proposal/finding/investigation
identity from trusted context and feeding a per-org proposal catalog
resolved from the tenant-bound action lifecycle (so acceptance and
planning validate identically). The retired command-execution,
autonomy, and command-shaped approval adapters are deleted; the router
wires the broker and catalog factories onto the AI settings handler.

The sixth side door is removed: PatrolService.generateRemediation-
PlanFromInvestigation and its call, which copied Fix.Commands into an
executable enterprise remediation plan, are deleted, pinned closed by a
source-audit test.

ActionCapabilityParamInfo gains Pattern (mapped by the broker), so the
canonical planner's pattern validation survives the cross-repo
boundary.

L20 readiness assertion RA35 asserts every Patrol-initiated
infrastructure mutation originates as a typed proposal and reaches
execution only through the canonical action lifecycle, with unsupported
proposals failing closed. Contract prose updated across ai-runtime,
api-contracts, agent-lifecycle, performance-and-scalability,
security-privacy, and storage-recovery.

Lands together with the pulse-enterprise orchestrator migration (the
replace directive means both heads move as one window).
2026-07-10 18:05:25 +01:00
rcourtman 26718e70b6 Add the typed Patrol action-proposal seam over the shared lifecycle
pkg/aicontracts gains the plan-only OrchestratorActionBroker contract:
ActionProposal (typed capability reference, no command, host, risk, or
approval fields), a read-only ActionCapabilityCatalog with parameter
sensitivity, ActionDisposition over the existing safe ActionPlanInfo
projection, and an additive Action *ActionReference on
InvestigationSession and InvestigationRecord. ProposedFix/ApprovalID are
documented as migration-only; OrchestratorDeps gains the ActionBroker
seam while CmdExecutor/ApprovalStore are marked legacy pending removal.

internal/api/patrol_action_broker.go implements the seam tenant-bound
over ResourceHandlers.ActionLifecycle(): fixed pulse_patrol actor,
broker-owned ActionOrigin stamped through the service's internal
PlanWithOptions (the public plan endpoint cannot claim an origin),
plan-only submission even for ApprovalNone capabilities, and refusal of
proposals that populate IsSensitive parameters before any persistence.

The lifecycle service adds Capabilities (same registry resolution and
typed errors as planning) and an OnActionTransition persisted-state
callback covering plan, decision, and terminal execution transitions,
published only after the store write succeeds, so Patrol can reconcile
decisions and outcomes deterministically. ActionAuditRecord carries the
new broker-owned Origin, persisted in action_audits.origin_json with a
schema migration and round-trip normalization.

Contracts updated across api-contracts, ai-runtime, unified-resources,
agent-lifecycle, and storage-recovery; proofs added in
pkg/aicontracts/contracts_test.go (propose-only method set, command-free
wire shape, additive reference), internal/api/contract_test.go
(plan-only broker pins), broker behavior tests, lifecycle origin and
transition tests, and a SQLite origin round-trip test.

Slice 2a of the typed-lifecycle enforcement ratchet: additive core
fabric only; enterprise migration and side-door deletion follow.
2026-07-10 11:56:54 +01:00
rcourtman 4c37a8c332 reporting: fit and wrap operator-controlled cover title and brand lines
Long custom titles on generate-multi covers overflowed the page edges;
the cover now shrinks to fit, wraps at the minimum size, and flows the
subtitle and scope blocks below a wrapped title. Also drop the stale
frontend-primitives contract and registry references to the
Settings/reportingResourceTypes.ts re-export removed in c862fb0ca; that
frontend-only commit skipped the governance audit, leaving the stale
references blocking every Go commit.
2026-07-10 00:42:06 +01:00
rcourtman f7aa9ee922 Remove unreachable exported functions from pkg/
Dead-code audit batch 1: five functions with zero references from main,
tests, or pulse-enterprise (deadcode -test, grep-verified per symbol,
enterprise compile-checked). Also renames the tlsutil test that
exercised the deleted compat alias's target to name the Unverified
function it actually calls.

Skipped from the audit list after re-verification: IsNilAlertPayload
(live pulse-enterprise consumer in internal/aialertanalysis).
2026-07-10 00:27:35 +01:00
rcourtman 255c7c23d4 Modernize Unified Agent lifecycle and platform support 2026-07-09 23:20:35 +01:00
rcourtman 042e7ef966 Harden remaining CodeQL security boundaries 2026-07-09 19:46:40 +01:00
rcourtman e8ad8b2fd3 Use checked int parsing for Proxmox conversions 2026-07-09 17:47:23 +01:00
rcourtman 92524e1c27 Harden CodeQL storage and integer boundaries 2026-07-09 17:37:08 +01:00
rcourtman 24b2e40e92 Harden scanned request and storage boundaries
Harden CodeQL-scanned request, command, path, and frontend sinks across relay proxying, availability probes, connection probing, notification CLI execution, report storage, licensing persistence, preview bootstrapping, tooltip rendering, logging, and test identity generation.
2026-07-09 17:22:29 +01:00
rcourtman 847cf98544 Fix activation fingerprint and license period status 2026-07-09 11:30:26 +01:00
rcourtman e23505206d Carry the max_users grant claim into instance license limits
GrantClaims and Claims gain a MaxUsers seat limit mirroring MaxGuests:
the grant JWT claim is copied at the activation bridge and surfaced
through EffectiveLimits()["max_users"], which the already-shipped
user-limit enforcement (MaxUsersLimitFromLicense) reads unchanged.
Inert by default: no plan sets max_users yet, so absent claim = 0 =
unlimited for every existing license and grant. The self-hosted
commercial volume scrub strips only max_monitored_systems and
max_guests; tests now pin that max_users survives it from both the
named field and explicit limits, plus the end-to-end proof that a grant
carrying max_users=3 enforces 3 and a grant without it stays unlimited.
Contract shape recorded in cloud-paid Extension Point 26; the grant
wire contract list gains max_users against the relay-server reference
(pulse-pro 95a18bd).
2026-07-08 16:56:07 +01:00
rcourtman fca5975c38 Add the dormant Business tier to the licensing core
TierBusiness: Pro's feature set, 365-day history retention, uncapped
core monitoring like every self-hosted tier, Business display name, and
a slot in the min-tier ordering. Differentiation is by max_users limit,
retention, and support rather than features (multi-user is already a Pro
capability via RBAC; see specs/pricing-segmentation.md binder findings).
Dormant until the license server issues business plan versions; no
checkout, pricing, or UI surface references it yet. Contract shape
recorded as cloud-paid Extension Point 26 and pinned by
TestBusinessTierContractShape.
2026-07-08 16:02:56 +01:00
rcourtman 983a89326f Block in-app self-update on the Pro binary to prevent silent downgrade
The in-app updater and install.sh both fetch the public community build from
github.com/rcourtman/Pulse. On non-Docker Pro installs (systemd, proxmoxve,
source) the "Apply Update" button was live, so applying an update replaced the
separately compiled Pro binary with community and silently stripped Audit,
RBAC, Reporting, and SSO from a paying customer. Docker was already blocked;
nothing else was.

Add a dependency-free pkg/edition marker (defaults to community) that the Pro
binary flips via enterpriseruntime.Initialize, mirroring the existing
coreaudit.SetLogger / server.SetBusinessHooks registration seam. ApplyUpdate
now refuses when the edition is Pro, pointing at the Private Release Access
portal (https://pulserelay.pro/download.html) and the install.sh --archive
path. The gate keys off the compiled binary, not license state: a community
binary with an active license still self-updates as before.

The update banner hides the in-app apply affordance and shows portal
instructions for the Pro runtime, keyed off the existing runtime-identity
signal (runtime.build) rather than a new payload field, so nothing extra is
plumbed through the version API and the frontend reuses the canonical
"which binary am I" contract. The backend gate is the hard guarantee; the
banner is the UX layer.

Guard 2 of the Pro download/update experience spec.
2026-07-08 10:02:04 +01:00
rcourtman c6fdd9058c Allow clearing SSO provider restriction fields from Settings
Empty allowedGroups, allowedDomains, allowedEmails, groupsClaim and
groupRoleMappings in a provider PUT were silently restored from the
existing config, so an admin could never clear them. The guards
shielded against lossy round-trips that no longer exist: the detail
GET and the flat list response both carry these fields, so an empty
value is an intentional clear. Nested OIDC/SAML config and the client
secret stay preserved since toggle payloads omit them and secrets are
never echoed in reads.

Also expose groupsClaim and groupRoleMappings in the shared extensions
list response so the enterprise list matches the core one, and make
the Settings enable/disable toggle send only writable fields; the old
list-item spread included computed response fields that the enterprise
strict PUT decoder rejects.
2026-07-07 22:04:46 +01:00
rcourtman 8355335e08 Clarify v5 migration recovery states 2026-07-07 21:46:07 +01:00
rcourtman 4382919447 Add MSP report scheduling and alert rollup 2026-07-07 20:37:18 +01:00
rcourtman 4a597fa425 Fix legacy audit monotonic timestamps
Refs #1464
2026-07-06 16:20:55 +01:00
rcourtman e7334e66e4 Allow local DNS agent HTTP URLs
Refs #1522
2026-07-06 13:53:19 +01:00
rcourtman 0f1211d510 Handle legacy audit timestamp rows 2026-07-05 23:18:37 +01:00
rcourtman 314429b879 Fix v5 agent update recovery
Issue: #1515
2026-07-05 10:08:19 +01:00
rcourtman cefc032a37 Fix patch-release follow-up regressions
Refs #1510

Refs #1501

Refs #1507

Refs #1442

- persist scoped workloads status filters across platform navigation
- derive host memory pressure from available memory
- reapply system settings after every monitor reload path
- bound PBS backup snapshot polling workers during large backup scans
2026-07-04 18:37:59 +01:00
rcourtman 5e9a94d375 Stabilize metrics rollup chunk proof
Keep the bounded rollup test inside raw retention and wait for startup maintenance before seeding source rows.
2026-07-03 21:57:14 +01:00
rcourtman 760eb32aaf Bound metrics rollup working set
Refs #1442
2026-07-03 20:35:02 +01:00
rcourtman 6344d17fd1 Allow local Pulse HTTP agent URLs
Refs #1505
2026-07-03 00:24:40 +01:00
rcourtman c0ac0762da Fix security scan findings
Harden proxy-auth admin role checks, metrics listener exposure, Teams webhook escaping, and dependency lockfiles.
2026-07-01 09:55:35 +01:00