Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
20 KiB
20 KiB
Scratchpad — Tactical RMM Integration
- Plan slug:
tacticalrmm-integration - Created:
2026-02-13
What This Is
Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan.
Prefer short bullets. Append new entries as you learn things, and also update earlier notes when a decision changes or an open question is resolved.
Decisions
- (2026-02-13) Tactical RMM integration must be available in both CE and EE (unlike NinjaOne which is currently EE-oriented).
- (2026-02-13) Assume Tactical beta API is enabled and use it for fleet sync (
/api/beta/v1/{client,site,agent}/). - (2026-02-13) Support 2 auth modes per tenant:
- API key:
X-API-KEY. - Username/password: Knox token with
Authorization: Token <token>, TOTP-aware viacheckcreds+login.
- API key:
- (2026-02-13) Tactical hierarchy mapping for sync:
- Tactical Client ~= “organization” =>
rmm_organization_mappings.external_organization_id. - Tactical Agent ~= device =>
assets.rmm_device_idandtenant_external_entity_mappings.external_entity_iduseagent_id(string). - Site id/name stored in mapping metadata (initially) to avoid schema churn.
- Tactical Client ~= “organization” =>
- (2026-02-13) Realtime sync is alert-driven only (Tactical webhooks are configured as alert actions). We will use alerts as the trigger to run “sync single agent”.
- (2026-02-13) Webhook auth uses
X-Alga-Webhook-Secretheader (Tactical supports custom headers on webhook actions).
Discoveries / Constraints
- (2026-02-13) Existing RMM platform tables are provider-agnostic:
rmm_integrations,rmm_organization_mappings,rmm_alerts,rmm_alert_rulescreated byserver/migrations/20251124000001_create_rmm_integration_tables.cjs. - (2026-02-13) Existing NinjaOne integration structure to mirror:
- Settings UI:
ee/server/src/components/settings/integrations/NinjaOneIntegrationSettings.tsx - Org mapping UI:
ee/server/src/components/settings/integrations/ninjaone/OrganizationMappingManager.tsx - Sync engine:
ee/server/src/lib/integrations/ninjaone/sync/syncEngine.ts - Webhook handler:
ee/server/src/lib/integrations/ninjaone/webhooks/webhookHandler.ts - Webhook route:
ee/server/src/app/api/webhooks/ninjaone/route.ts(re-exported fromserver/src/app/api/webhooks/ninjaone/route.ts)
- Settings UI:
- (2026-02-13) Tactical has an additional status state
overdue. Current Alga types/UI generally assumeonline|offline|unknown, so Tactical likely requires a small type/UI expansion. - (2026-02-13) Tactical beta agent list serializer is
__all__model fields; computedstatusmay not appear on list responses. Prefer computing status from last_seen/offline_time/overdue_time for list-based sync. - (2026-02-13) Fleet-scale software inventory ingestion should use bulk
GET /api/software/(cached) rather than per-agent refreshPUT /api/software/<agent_id>/. - (2026-06-17) CORRECTION, verified against a live Tactical (v0.0.203): stock Tactical serves its API at the root of the
api.host, not under/api/. The integration's paths were wrong by an/api/prefix and were corrected in code, tests, and the EE workflow runtime:/api/beta/v1/...->/beta/v1/...,/api/v2/...->/v2/...,/api/alerts/->/alerts/,/api/software/->/software/. Two more gotchas surfaced during manual testing:- The instance URL must be the
api.host. Thermm.(dashboard) host serves the SPAindex.htmlwith a200for every path, so sync parses zero records and looks like a no-op. The settings UI default placeholder usedhttps://rmm.example.com, which steered users wrong; it now useshttps://api.example.com. - The beta API is off by default.
BETA_API_ENABLEDgatespath("beta/v1/", ...)in Tactical'surls.py, so/beta/v1/*returns404until you setBETA_API_ENABLED = Trueand restart Tactical.
- The instance URL must be the
Commands / Runbooks
- (2026-02-13) Plan validation:
python3 /Users/roberisaacs/.codex/skills/alga-plan/scripts/validate_plan.py ee/docs/plans/2026-02-13-tacticalrmm-integration
Links / References
- Tactical API docs (user provided):
docs.tacticalrmm.com/functions/api/and related pages. - Integrations settings entry point:
packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.tsx - Assets RMM status indicator (CE+EE):
packages/assets/src/components/RmmStatusIndicator.tsx - API auth middleware skip list (add
/api/webhooks/tacticalrmm):server/src/middleware.ts
Test Notes
- (2026-02-13) T080: Added bulk software ingest test verifying Tactical ingestion uses only
GET /api/software/(no per-agent refreshPUT) and writes to normalized software tables (software_catalog,asset_software). Files:server/src/test/unit/tacticalrmm/tacticalSoftwareIngest.bulk.test.ts
- (2026-02-13) T081: Extended software ingest test to assert agent_id to asset association via
tenant_external_entity_mappings(unmapped agents do not produce catalog/asset_software rows). - (2026-02-13) T082: Extended software ingest test to assert idempotency (rerun does not duplicate
software_catalogorasset_softwarerows). - (2026-02-13) T090: Added coverage for Tactical event-bus publishing:
- Org sync publishes
RMM_SYNC_STARTEDandRMM_SYNC_COMPLETED, and now publishesRMM_SYNC_FAILEDon exception (best-effort). - Webhook route publishes
RMM_WEBHOOK_RECEIVEDon valid webhook requests. - Files:
packages/integrations/src/actions/integrations/tacticalRmmActions.tsserver/src/test/unit/tacticalrmm/tacticalEvents.published.test.ts
- Org sync publishes
- (2026-02-13) T092: Webhook route behavior for unknown/unmapped
agent_idis explicitly covered: returns 200 and persistsrmm_alertswithasset_id=nullwhen no mapping exists. File:server/src/test/unit/tacticalrmm/tacticalWebhook.upsertAlert.test.ts
- (2026-02-13) T094: Added unit coverage that
TacticalRmmClient.listAllBetaaccepts a non-paginated (array) response body for/api/beta/v1/client/and does not attempt to page further. File:server/src/test/unit/tacticalrmm/tacticalApiClient.pagination.test.ts
- (2026-02-13) T093: Added paging smoke test for Tactical device sync verifying multi-page beta responses are iterated (sites + agents) and all agents across pages are processed. File:
server/src/test/unit/tacticalrmm/tacticalDeviceSync.paginationSmoke.test.ts
- (2026-02-13) T091: Added Playwright E2E coverage for the Tactical webhook config UI showing header name and payload template (must include
agent_id). File:server/src/test/e2e/integrations-tactical-webhook-config.playwright.test.ts
Open Questions
- Should we mark assets inactive when an agent disappears from Tactical inventory, or leave as-is unless explicitly deleted?
- Should Tactical “overdue” be first-class everywhere (recommended) or mapped to offline?
- What is the best heuristic for workstation vs server classification from Tactical agent fields?
Deletion Policy
- (2026-02-13) F055: Device deletion handling is currently skipped (no auto-inactivation on missing agents). Rationale: avoid accidental deactivation without a complete snapshot + explicit user confirmation.
Progress Log
- (2026-02-13) F001: Added
tacticalrmmtoRmmProviderunions inpackages/types,server(CE), andee/server(EE) so it can be persisted/rendered consistently. - (2026-02-13) F002: Centralized RMM provider display-name mapping in
packages/assetsand added friendly labelTactical RMMfortacticalrmm(used by status indicator + asset header badge). - (2026-02-13) F003: Added
overdueto agent status unions and UI surfaces (dashboard filter, status indicator/badge, vitals panel) plus backend schema validation; treatedoverdueas non-online for remote access and health status. - (2026-02-13) F010: Added Tactical RMM as a first-class integration entry under Settings -> Integrations -> RMM in
packages/integrations(rendered in both CE + EE). - (2026-02-13) F011: Implemented Tactical RMM settings panel UI (instance URL, auth mode, credential inputs, save/test/disconnect flows) in
packages/integrations. - (2026-02-13) F012: Added Tactical connection status panel (connected/disconnected, last sync, counts, last error) backed by a server action summary query.
- (2026-02-13) F013: Added "Sync Clients" UI + server action to pull Tactical
/api/beta/v1/client/and upsertrmm_organization_mappingswith created/updated/failed counts. - (2026-02-13) F014: Added "Sync Devices" UI + server action to fetch Tactical agents for mapped orgs and upsert Alga assets + external entity mappings with summary counts.
- (2026-02-13) F015: Implemented org mapping UI using
ClientPicker+ per-org auto-sync toggle, backed by list/update server actions forrmm_organization_mappings. - (2026-02-13) F016: Implemented webhook config UI (URL,
X-Alga-Webhook-Secret, payload template) with per-tenant secret generation via secret store. - (2026-02-13) F017: Added UI + server action to backfill Tactical alerts into
rmm_alertsand display sync results. - (2026-02-13) F018: Added UI + server action to ingest Tactical bulk cached software inventory into
software_catalog+asset_softwareusing external mappings (no per-agent refresh). - (2026-02-13) F020: Added server actions to save/read Tactical settings and credential status with masking; disconnect clears tenant secrets.
- (2026-02-13) F021: Implemented Tactical connection test for both API key and Knox modes, including TOTP-required detection.
- (2026-02-13) F022: Implemented Tactical
rmm_integrationsupsert/update flows (instance URL, is_active, auth_mode in settings). - (2026-02-13) F023: Implemented Tactical connection status summary (mapped orgs, synced devices, active alerts, status counts) for the settings status panel.
- (2026-02-13) F030: Added Tactical API client wrapper (
TacticalRmmClient) with base URL normalization + shared request/pagination helpers. - (2026-02-13) F031: API key auth implemented via
X-API-KEYheader in Tactical API client. - (2026-02-13) F032: Knox auth implemented (checkcreds + login with optional TOTP) and token persisted in tenant secrets; requests use
Authorization: Token .... - (2026-02-13) F033: Knox mode retries once on 401 by refreshing the token and retrying the request (single refresh guard).
- (2026-02-13) F034: Implemented beta pagination helper (
listAllBeta) loopingpageuntilnextis null withpage_sizecapped at 1000. - (2026-02-13) F040: Implemented Tactical client inventory sync into
rmm_organization_mappingsvia/api/beta/v1/client/. - (2026-02-13) F041: Implemented Tactical org mapping list/update server actions (client assignment +
auto_sync_assets). - (2026-02-13) F050: Implemented Tactical full device sync (sites + agents) for mapped orgs; upserts assets and updates RMM fields.
- (2026-02-13) F051: Implemented deterministic Tactical agent status computation (
online|offline|overdue) from last_seen/offline_time/overdue_time. - (2026-02-13) F054: Implemented Tactical external entity mapping upserts to
tenant_external_entity_mappingswithexternal_entity_id=agent_id,external_realm_id=client_pk, and site metadata. - (2026-02-13) F052: Mapped Tactical agent OS/version + agent version into workstation/server extension rows during device sync; base asset RMM fields and last_seen/last_rmm_sync are set.
- (2026-02-13) F053: Mapped Tactical agent vitals (current user, uptime, LAN/WAN IP) into cached RMM extension fields when present on list responses.
- (2026-02-13) F056: Added targeted single-agent sync server action by
agentId(refreshes asset fields + extension vitals). - (2026-02-13) F060: Added
POST /api/webhooks/tacticalrmmroute (node runtime) and exempted it from API-key middleware. - (2026-02-13) F061: Webhook validates
X-Alga-Webhook-Secret(case-insensitive) against per-tenant secret store before processing. - (2026-02-13) F062: Documented Tactical webhook JSON contract in
ee/docs/plans/2026-02-13-tacticalrmm-integration/WEBHOOK_CONTRACT.mdand surfaced a payload template in the settings UI. - (2026-02-13) F063: Webhook upserts
rmm_alertsfortacticalrmmand associatesasset_idwhen an agent external mapping exists. - (2026-02-13) F064: Webhook triggers a best-effort targeted single-agent refresh to update cached vitals/status after alert events.
- (2026-02-13) F070: Implemented Tactical alerts backfill via
PATCH /api/alerts/and upsert intormm_alerts(current default: active alerts). - (2026-02-13) F071: Implemented Tactical bulk software ingestion via
GET /api/software/intosoftware_catalog+asset_softwareusing Tactical agent_id mappings (no per-agent refresh calls). - (2026-02-13) F080: Published event-bus events for Tactical sync and webhook flows (RMM_SYNC_STARTED/COMPLETED, RMM_WEBHOOK_RECEIVED) on a best-effort basis.
- (2026-02-13) T001: Added a types package typecheck test ensuring
RmmProvideracceptstacticalrmm. - (2026-02-13) T002: Fixed
@alga-psa/assetsunit test runner (migrated@nx/vite:test->@nx/vitest:testwithpackages/assets/vitest.config.ts) and added a unit test assertingtacticalrmmrenders asTactical RMMviagetRmmProviderDisplayName. - (2026-02-13) T003: Added typecheck coverage for
RmmAgentStatusincludingoverduein@alga-psa/types, and refactored the asset dashboard agent-status filter options intopackages/assets/src/lib/rmmAgentStatusOptions.tswith a unit test ensuringoverdueis present. - (2026-02-13) T010: Added CE Playwright E2E coverage asserting Settings -> Integrations -> RMM renders
Tactical RMM, and fixed CE Playwright auth cookie helper to align with@alga-psa/auth/sessioncookie naming (dev mode) and URL-scoped cookie injection. - (2026-02-13) T011: Added EE Playwright E2E coverage asserting Settings -> Integrations -> RMM renders
Tactical RMM; fixed workflow-worker docker build dependencies for Playwright (added@alga-psa/portal-shared) and addressed TS compile issues uncovered by the Playwright workflow-worker build. - (2026-02-13) T012: Added CE Playwright E2E coverage for saving Tactical instance URL + API key and asserting masked credential status persists (placeholder + “Saved:” last-4) using a filesystem-backed Playwright secret store (
SECRET_FS_BASE_PATH=secrets-playwright). - (2026-02-13) T013: Added CE Playwright E2E coverage for Knox auth (username/password, no TOTP) using an in-test mock Tactical HTTP server (checkcreds/login/client list). Assertion targets the “Knox token saved” masked status (vs. the success alert, which may be clipped by scroll container overflow) and verifies the backend uses
Authorization: Token <knox>when calling the beta clients endpoint. - (2026-02-13) T014: Added CE Playwright E2E coverage for the TOTP-required Knox flow using a mock Tactical server (
checkcredsreturns{totp:true}). Test asserts the first connection test prompts for a TOTP input and does not attempt login; then withtwofactorprovided it completes login and persists the Knox token. - (2026-02-13) T015: Added CE Playwright E2E coverage for disconnect: configures Tactical with API key against a mock server, tests connection to mark
rmm_integrations.is_active=true, then disconnects and asserts the API key secret is cleared (placeholder resets, “Saved:” line gone) andrmm_integrations.is_active=falsewithconnected_at=null. - (2026-02-13) T020: Added a unit test for
getTacticalRmmSettingssecret masking using Vitest module mocks (bypassingwithAuthand stubbing DB/secret provider) to assert only the last 4 characters are visible. Updatedserver/package.jsonto alignvitestwith workspace v4, updated rootpackage-lock.json, and added missing Vitest alias for@alga-psa/event-bus/publishersrequired by integration action imports. - (2026-02-13) T021: Added a unit/integration test for
testTacticalRmmConnection(API key mode) assertingX-API-KEYis used and 401 errors are surfaced asUnauthorized (401): invalid credentials or token expired.(axios mocked). - (2026-02-13) T022: Updated Knox connection test action to retry login once if the token verification GET returns 401. Added a unit/integration test asserting
Authorization: Token ...is used and the retry persists the refreshed token. - (2026-02-13) T023: Added an integration-style unit test that exercises
saveTacticalRmmConfigurationtwice with a mocked Knex upsert chain to ensure only onermm_integrationsrow exists per tenant+provider and subsequent saves update the existing row (stable integration_id, instance_url updated). - (2026-02-13) T024: Added a unit/integration test for
getTacticalRmmConnectionSummarywith a table-switching Knex mock to validate mapped org/device/active alert counts and agent_status breakdown mapping (null->unknown). - (2026-02-13) T030: Added unit tests for
normalizeTacticalBaseUrlcovering protocol defaults, trailing slash removal, and/apisegment stripping behavior. - (2026-02-13) T031: Added a unit test for
TacticalRmmClient.listAllBetaverifying DRF-style pagination loops pages untilnext=nulland capspage_sizeat 1000. - (2026-02-13) T032: Added unit tests for the Knox connection test flow ensuring
checkcreds.totp=trueresults in aloginrequest withtwofactor, whiletotp=falselogs in without twofactor. - (2026-02-13) T033: Added a unit test for the Tactical API client's Knox 401 retry behavior, asserting it refreshes at most once and uses the refreshed
Authorization: Token ...on the retry. - (2026-02-13) T040: Added an integration-style unit test for
syncTacticalRmmOrganizationsusing a mocked Tactical API client and an in-memoryrmm_organization_mappingsstore to verify upsert/merge behavior and created/updated counters across reruns. - (2026-02-13) T041: Added CE Playwright E2E coverage for Tactical org mapping assignment using
ClientPickeroption ids; assertsrmm_organization_mappings.client_idpersists after selecting an Alga Client. - (2026-02-13) T042: Added CE Playwright E2E coverage for toggling
auto_sync_assetsvia the org mapping switch; test forces a mapping refresh after toggles to ensure the UI reflects persisted DB state before asserting DB values. - (2026-02-13) T050-T053: Added unit tests for
computeTacticalAgentStatuscovering online/offline/overdue/null-last_seen rules using a fixednowtimestamp. - (2026-02-13) T054: Added an integration-style unit test for
syncTacticalRmmDevicesusing a fake Knex + mocked Tactical beta API client to validate it creates a new asset for an unmapped agent and writestenant_external_entity_mappingswithexternal_entity_id=agent_id. - (2026-02-13) T055: Extended the full device sync test to cover the update path when a mapping exists, asserting asset fields (name/status/last_seen/last_rmm_sync) are refreshed on rerun.
- (2026-02-13) T056: Extended the full device sync create-path assertion to validate site id/name are stored in
tenant_external_entity_mappings.metadataand the Tactical client id is stored asexternal_realm_id. - (2026-02-13) T057: Added an integration-style unit test for
syncTacticalRmmSingleAgentusing a fake Knex + mocked TacticalGET /api/beta/v1/agent/<id>/to assert the linked asset and external mapping are updated (including site metadata). - (2026-02-13) T058: Added an integration-style unit test asserting the Tactical device deletion policy is "skip": when an agent disappears from list responses, the sync does not delete or inactivate the existing asset/mapping (items_deleted stays 0).
- (2026-02-13) T060: Added unit tests for the Tactical webhook route handler ensuring it does not require API-key auth and enforces
X-Alga-Webhook-Secret(401 on missing/invalid secret). - (2026-02-13) T061-T062: Added unit tests for webhook ingestion verifying minimal payload upserts
rmm_alertsand thatasset_idis populated only when an external mapping exists. - (2026-02-13) T063: Added an integration-style unit test exercising the webhook handler with the real
syncTacticalSingleAgentForTenanthelper (mocked Tactical API + fake Knex) to assert the webhook both records an alert and refreshes asset vitals/status. - (2026-02-13) T070-T071: Added an integration-style unit test for alerts backfill that mocks the Tactical
PATCH /api/alerts/response and verifies idempotent upsert intormm_alertsplus mappingagent_idtoasset_idviatenant_external_entity_mappingswhen available.