- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift) - LogLift evidence pipeline (migration 078): upload webhook, B2 storage client, receiver/matcher, EventLogCollector PowerShell script - IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket xrefs, applications/configurations browse pages + apply/revert/audit endpoints - Link-aware analyzer bundles (migration 073) + provider toggle (migration 074): link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion panels, analyze-bundle endpoint - Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts admin page, reconciler service, resolve endpoints - Dashboard overhaul: integration-health service + alerts, overview/health endpoints - Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
4.1 KiB
SQL
86 lines
4.1 KiB
SQL
-- Backfill device_external_ids.company_id for sources that have a known
|
|
-- per-tool → Autotask-company mapping table. Populating company_id unlocks
|
|
-- the reconciler's hostname-in-company strategy for s1 + veeam + datto_rmm
|
|
-- rows that had no serial.
|
|
--
|
|
-- Mappings used:
|
|
-- datto_rmm → datto_rmm_devices.site_uid → rmm_site_mappings.company_id
|
|
-- s1 → s1_agents.site_id → s1_company_mappings.company_id
|
|
-- itglue → itg_configurations.organization_id → itg_organizations.psa_id (cast)
|
|
-- where psa_integration = 'autotask'
|
|
-- veeam → no clean mapping today; left null.
|
|
--
|
|
-- Also adds device_link_review to capture reconciler conflicts (multiple
|
|
-- candidate CIs) for admin resolution.
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- 1. company_id backfill — datto_rmm
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
UPDATE device_external_ids dx
|
|
SET company_id = m.company_id
|
|
FROM datto_rmm_devices d
|
|
JOIN rmm_site_mappings m ON m.rmm_site_uid = d.site_uid
|
|
WHERE dx.source = 'datto_rmm'
|
|
AND dx.source_id = d.uid
|
|
AND dx.company_id IS NULL;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- 2. company_id backfill — s1
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
UPDATE device_external_ids dx
|
|
SET company_id = m.company_id
|
|
FROM s1_agents sa
|
|
JOIN s1_company_mappings m ON m.s1_site_id = sa.site_id
|
|
WHERE dx.source = 's1'
|
|
AND dx.source_id = sa.id::text
|
|
AND dx.company_id IS NULL;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- 3. company_id backfill — itglue
|
|
-- ---------------------------------------------------------------------------
|
|
-- itg_organizations.psa_id is unpopulated in this environment. Fall back to
|
|
-- fuzzy name match against companies — the same approach asset-matcher.ts
|
|
-- and itglue-redact.ts use elsewhere in the codebase. Imperfect but consistent.
|
|
UPDATE device_external_ids dx
|
|
SET company_id = c.id
|
|
FROM itg_configurations igc
|
|
JOIN itg_organizations ig ON ig.id = igc.organization_id
|
|
JOIN companies c ON LOWER(c.company_name) = LOWER(ig.name)
|
|
WHERE dx.source = 'itglue'
|
|
AND dx.source_id = igc.id::text
|
|
AND dx.company_id IS NULL;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- 4. device_link_review — conflicts queue for admin resolution
|
|
-- ---------------------------------------------------------------------------
|
|
-- One row per (xref-row, batch) when the reconciler finds 2+ CI candidates.
|
|
-- Resolving = the admin picks one CI; the reconciler's match logic doesn't
|
|
-- get to decide on its own.
|
|
|
|
CREATE TABLE IF NOT EXISTS device_link_review (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
device_external_id BIGINT NOT NULL REFERENCES device_external_ids(id) ON DELETE CASCADE,
|
|
candidate_ci_ids BIGINT[] NOT NULL,
|
|
match_confidences TEXT[] NOT NULL,
|
|
detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
resolved_at TIMESTAMPTZ,
|
|
resolved_by_user_id TEXT NULL REFERENCES "user"(id) ON DELETE SET NULL,
|
|
resolved_to_ci_id BIGINT NULL REFERENCES configuration_items(id) ON DELETE SET NULL,
|
|
resolution_note TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_device_link_review_unresolved
|
|
ON device_link_review(detected_at DESC) WHERE resolved_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS ix_device_link_review_xref
|
|
ON device_link_review(device_external_id);
|
|
|
|
-- Don't keep stale rows: when the underlying xref row gets linked some other
|
|
-- way (admin manually fixes the source-of-truth), a unique index would block
|
|
-- new reviews. Instead we use a partial index for "open reviews per xref".
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_device_link_review_open_per_xref
|
|
ON device_link_review(device_external_id) WHERE resolved_at IS NULL;
|
|
|
|
COMMENT ON TABLE device_link_review IS
|
|
'Reconciler conflicts queue: one row per unlinked device_external_ids row that matches 2+ configuration_items. Admin picks the right CI; reconciler does not auto-merge.';
|