wulf-pulse/migrations/075_itglue_audit.sql
lorentz 1112a06afe feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- 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>
2026-05-03 07:13:18 -04:00

87 lines
4.1 KiB
SQL

-- IT Glue asset audit + write-back tables.
-- See docs/wulf-pulse-ticket-analyzer-build-notes.md → Phase 4.
--
-- Two tables:
-- 1. itglue_asset_audits — one row per audit run (LLM gap analysis).
-- 2. itglue_writes — one row per write attempt (one field, one moment),
-- with provenance back to the audit that prompted it.
--
-- A separate generic audit_log row is also written by the API layer
-- (lib/services/audit.ts) so /admin/audit-log surfaces every IT Glue change
-- alongside other admin actions. The two domain-specific tables here exist
-- because:
-- - audits accumulate full LLM context snapshots (heavy, infrequent),
-- - writes are atomic per-field decisions with before/after diffs that the
-- generic audit_log's free-form JSONB doesn't capture cleanly.
CREATE TABLE IF NOT EXISTS itglue_asset_audits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
asset_type TEXT NOT NULL CHECK (asset_type IN ('flexible_asset')),
asset_id BIGINT NOT NULL,
asset_type_id BIGINT,
organization_id BIGINT,
generated_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL,
generated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
provider TEXT NOT NULL DEFAULT 'anthropic'
CHECK (provider IN ('anthropic','openrouter')),
model_used TEXT,
asset_snapshot JSONB NOT NULL,
ticket_count INT NOT NULL,
field_gaps JSONB NOT NULL,
notes_promotions JSONB NOT NULL,
contradictions JSONB NOT NULL,
overall_score NUMERIC(3,2),
estimated_cost_usd NUMERIC(10,4),
total_input_tokens INT,
total_output_tokens INT,
status TEXT NOT NULL DEFAULT 'complete'
CHECK (status IN ('pending','running','complete','failed')),
error_message TEXT
);
CREATE INDEX IF NOT EXISTS ix_itglue_asset_audits_asset
ON itglue_asset_audits (asset_type, asset_id, generated_at DESC);
CREATE INDEX IF NOT EXISTS ix_itglue_asset_audits_org
ON itglue_asset_audits (organization_id, generated_at DESC);
COMMENT ON TABLE itglue_asset_audits IS
'One row per LLM-driven audit of an IT Glue asset against ticket history. '
'Captures the full input snapshot (asset traits at the time, redacted), the '
'extracted gaps/promotions/contradictions, and cost. Forever-retained.';
COMMENT ON COLUMN itglue_asset_audits.field_gaps IS
'JSON array: [{ field_name, why_missing_matters, suggested_value, '
'evidence_ticket_numbers, confidence }]';
COMMENT ON COLUMN itglue_asset_audits.notes_promotions IS
'JSON array: [{ quoted_note_text, target_field, suggested_value, confidence }]';
COMMENT ON COLUMN itglue_asset_audits.contradictions IS
'JSON array: [{ description, evidence }]';
CREATE TABLE IF NOT EXISTS itglue_writes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
audit_id UUID REFERENCES itglue_asset_audits(id) ON DELETE SET NULL,
asset_type TEXT NOT NULL CHECK (asset_type IN ('flexible_asset')),
asset_id BIGINT NOT NULL,
field_name TEXT NOT NULL,
before_value JSONB,
after_value JSONB NOT NULL,
performed_by_user_id TEXT REFERENCES "user"(id) ON DELETE SET NULL,
performed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status TEXT NOT NULL
CHECK (status IN ('pending','committed','failed','reverted')),
itglue_response JSONB,
error_message TEXT,
source_evidence JSONB
);
CREATE INDEX IF NOT EXISTS ix_itglue_writes_asset
ON itglue_writes (asset_type, asset_id, performed_at DESC);
CREATE INDEX IF NOT EXISTS ix_itglue_writes_user
ON itglue_writes (performed_by_user_id, performed_at DESC);
CREATE INDEX IF NOT EXISTS ix_itglue_writes_audit
ON itglue_writes (audit_id);
COMMENT ON TABLE itglue_writes IS
'One row per write attempt to IT Glue. Pending → committed | failed | reverted. '
'Reverts produce a new row whose before_value/after_value swap the original; '
'the original row gets status=reverted. Forever-retained.';