1. Compliance CSV/PDF: Replace non-existent pd.total_packages with
jsonb_array_length(pd.installed_packages) and pd.pending_patches
with pd.patch_count. Fix GROUP BY to match new columns.
2. Vulnerability CSV/PDF: Replace non-existent pd.cve_data with
jsonb_array_elements on pd.available_patches JSONB, extracting
cve_ids via nested lateral join. Replace pd.updated_at with
pd.polled_at (actual column name).
3. TypeScript: Remove duplicate PollingConfig interface declaration
in frontend/src/types/index.ts.
4. ReportsPage: Replace Group ID text field with Select dropdown
populated from GET /api/v1/groups, showing group names instead
of requiring UUID input.
5. PDF charts: Increase embed_image scale from 0.18 to 0.28 for
better visibility on A4 landscape pages.
6. Vulnerability CSV: Remove invalid (no data) comment row on
query failure; return header-only CSV instead to maintain valid
CSV format.
- debian/postinst: auto-restart patch-manager-web and patch-manager-worker
on upgrade (not fresh install)
- debian/postinst: list pending database migrations after upgrade
- scripts/build-package.sh: update debian/control Version from VERSION
variable to ensure dpkg handles upgrades correctly
- tasks/lessons.md: added lessons about service restarts and version sync
- Add target_host_id column to host_health_checks table (nullable UUID FK)
- Allow service checks to query a different host agent
- Backend models, API routes, and poller updated
- Frontend: host selector dropdown for service checks
- Validation: target host must exist and be healthy
- FK ON DELETE SET NULL: revert to own host if target deleted
- Added HealthCheckListResponse type { checks: [...], total: number }
- Updated healthChecksApi.list() return type to HealthCheckListResponse
- Fixed HostDetailPage to use res.data?.checks instead of Array.isArray
- Added Target column to health checks table
- Added git pre-commit/pre-push hooks to prevent format CI failures
- Updated lessons.md
- Fixed data extraction: use res.data?.checks instead of Array.isArray(res.data)
- Added Target column to health checks table showing service_name or URL
- Matches the pattern used by maintenance windows (res.data?.windows)
The ip_address column is PostgreSQL inet type. When cast to text
(ip_address::text), it includes the CIDR mask (e.g., 192.168.0.166/32).
Rust IpAddr::parse() fails on CIDR notation, so the IP SAN was silently
skipped in server certificates.
Fix: use host(ip_address) in SQL queries to strip the CIDR mask,
returning just the IP address (e.g., 192.168.0.166).
Affected endpoints:
- POST /hosts/:id/certificates (issue_client_cert)
- POST /hosts/:id/certificates/reissue (reissue_host_cert)
- Generate internal CA (ECDSA P-256, 10-year validity) if not present
- Sign web server cert with internal CA (1-year validity)
- Add SANs for hostname, short hostname, localhost, and host IP
- Add EKU: serverAuth to web cert
- pm-ca will load existing CA on startup
- Simplify host cert section to only show agent deployment files
- Remove client cert/key from KeyDisplayDialog on host detail page
- Bundle download now only contains ca.crt, server.crt, server.key
- Rename bundle to {hostname}-agent-certs.zip
- Remove standalone client cert download button
- Update dialog title and warning text
- The main Certificates page still has all certs available
Bug 1: status.output.as_deref() passes NULL when agent returns no output,
but patch_job_hosts.output has NOT NULL constraint.
Fix: use .unwrap_or("") to default to empty string.
Bug 2: sync_job_status passes String to job_status enum column,
PostgreSQL rejects implicit text-to-enum cast.
Fix: add ::job_status cast in SQL UPDATE queries.
- Fixed broken SQL in hosts.rs that was using Python string replacement
instead of proper CASE expression for health_check_status
- Added serde default for health_check_poll_interval_secs config
- Fixed missing AgentClient import in health_check_poller.rs
- Added /etc/patch-manager/keys to systemd ReadWritePaths
- Integration test verified: health check CRUD, service/HTTP checks work
- Added health_check_poller.rs: periodic service/HTTP health checks
- Added pre-patch health gate in job_executor.rs
- Added waiting_health_check job status (migration 008)
- Added health_check_status to HostSummary and hosts API
- Added health check types and API functions to frontend
- Added health check UI section to HostDetailPage
- Added health check status indicators to HostsPage and PatchDeploymentPage
- Added serde default for health_check_poll_interval_secs
- Fixed missing AgentClient import in health_check_poller.rs
- Fixed missing ws_relay import in main.rs
- Fixed missing closing paren in retry_pending_jobs SQL
- Added ReadWritePaths for /etc/patch-manager/keys in systemd services
- ws_relay.rs: Add ALPN protocol http/1.1 to rustls ClientConfig to prevent
HTTP/2 negotiation which breaks WebSocket upgrades (Sec-WebSocket-Accept mismatch)
- ws_relay.rs: Add detailed TLS error chain logging for debugging connection failures
- ws_relay.rs: Add HTTP polling fallback when WebSocket connection fails, using
AgentClient to poll /api/v1/jobs/{id} every ws_relay_poll_interval_secs
- config.rs: Add ws_relay_poll_interval_secs field (default: 10 seconds)
- config.example.toml: Add ws_relay_poll_interval_secs documentation
- jobs.rs: Fire pg_notify with event_type job on cancel
- job_executor.rs: Fire pg_notify with event_type job when parent job transitions
- ws_relay.rs: Add event_type field to NotifyPayload (host vs job events)
- Frontend: Add event_type, succeeded_count, failed_count, host_count to JobWsEvent
- Frontend: handleWsEvent distinguishes host vs job events for accurate status updates
Worker was crashing on startup with:
Could not automatically determine the process-level CryptoProvider
Added rustls::crypto:💍:default_provider().install_default()
to pm-worker main.rs, matching the pm-web initialization.
- Frontend: handleWsEvent now distinguishes host vs job events
- Host events only update detail rows + optimistic counters
- Job events (event_type=job) set authoritative status + counts
- Backend ws_relay: NotifyPayload now includes event_type field
- Host events: event_type=host
- update_parent_job_status fires pg_notify with event_type=job
- Backend job_executor: sync_job_status fires pg_notify with event_type=job
- Backend jobs cancel endpoint fires pg_notify with event_type=job
- Fixes jobs appearing stuck because host status was mapped to job status
BUG-15: Empty patch_selection sent to agent as-is, causing apply nothing
instead of apply all available patches per SPEC. When packages is empty,
now query host_patch_data and expand to full package list.
BUG-16: refresh_listener used INSERT instead of UPSERT for
host_patch_data, causing duplicate key constraint errors.
BUG-14: Patch poller was INSERT-ing a new row every poll cycle instead of
UPSERT-ing, creating 51 duplicate rows in host_patch_data for a single host.
Changes:
- patch_poller.rs: Changed INSERT to INSERT...ON CONFLICT (host_id) DO UPDATE
so each host only has one row that gets updated on each poll
- Migration 006: Added UNIQUE constraint on host_id, cleaned up 50 duplicate
rows keeping only the latest polled_at per host
The dashboard showing 174 pending patches and 0% compliance is expected
behavior - the patch data was collected before the job ran and the poller
runs every 30 minutes. The next poll cycle will refresh the data.
The linux_patch_api agent returns "completed" as its terminal success
status, but the worker only recognized "succeeded". This caused the
worker to log "unexpected agent status — ignoring" every 60 seconds
and never mark patch jobs as finished.
Changes:
- job_executor.rs: match "completed" alongside "succeeded" as terminal
success status, mapping both to patch_job_hosts.status = succeeded
- job_executor.rs: match "cancelled" as a terminal failure status,
routing to handle_host_failure with appropriate error message
- pm-agent-client types.rs: updated AgentJobStatus doc comment to
list all valid agent statuses: queued, running, succeeded, completed,
failed, cancelled
- Added Navigation subsection under User Interface:
- Layout structure: AppBar + 240px sidebar + main content
- Menu groups: Overview, Fleet, Operations, Administration
- RBAC visibility rules for admin-only items
- Mobile responsive behavior (collapsible drawer)
- User menu with avatar, role display, and sign out
- Dark theme specification (Primary: #42A5F5, Secondary: #26C6DA)
- Added Frontend Error Handling subsection:
- Login error messages for network, rate limit, auth, MFA, server errors
- Dismissible MUI Alert components (no blank error pages)
- Auth token expiry handling via Zustand store (no hard redirects)
- React Router RequireAuth guard behavior