GRC Issue Summarization

SkillDev tools

Summarize GRC issues with context including related risks, controls, compliance gaps, and business impact to generate executive-ready summaries for audit committees

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the GRC Issue Summarization skill

What this skill tells your AI

The instructions your AI receives, as published by happy-technologies-llc/happy-platform-skills in skills/grc/issue-summarization/SKILL.md and read by ahel’s review.

Overview

This skill generates contextual summaries of Governance, Risk, and Compliance (GRC) issues in ServiceNow. It covers:

  • Aggregating GRC issue data with related risks, controls, and compliance gaps
  • Assessing business impact by correlating issues to profiles and policy violations
  • Generating executive-ready summaries suitable for audit committees and board reporting
  • Producing trend analysis showing issue volume, aging, and resolution rates
  • Creating compliance gap narratives tied to regulatory citations

When to use:

  • Preparing executive briefings or audit committee reports
  • When stakeholders need a consolidated view of GRC posture
  • During quarterly or annual compliance reviews
  • When summarizing findings from internal or external audits
  • Before risk committee meetings requiring issue status overviews

Prerequisites

  • Roles: sn_grc.viewer, sn_grc.manager, sn_compliance.manager, or admin
  • Plugins: com.sn_grc, com.sn_compliance, com.sn_risk
  • Access: Read access to sn_grc_issue, sn_grc_risk, sn_compliance_control, sn_grc_profile tables
  • Knowledge: Understanding of organizational compliance frameworks and reporting requirements

Key GRC Tables

TablePurposeKey Fields
sn_grc_issueGRC issues and findingsnumber, short_description, state, priority, risk_rating, profile, item, category, source
sn_grc_riskRisk records linked to profilesnumber, short_description, risk_score, state, treatment, residual_risk, inherent_risk
sn_compliance_controlCompliance controlsnumber, short_description, state, control_objective, owner, effectiveness, test_result
sn_compliance_policyCompliance policiesnumber, short_description, state, owner, policy_statement, effective_date
sn_grc_profileGRC entity profilesnumber, short_description, profile_type, applies_to
sn_compliance_citationRegulatory citationsnumber, short_description, reference, regulation, authority

Procedure

Step 1: Retrieve GRC Issues for Summarization

Query GRC issues filtered by scope (all open, specific category, or date range).

Using MCP (Claude Code/Desktop):

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_issue
  query: active=true^ORDERBYDESCpriority
  fields: sys_id,number,short_description,description,state,priority,risk_rating,profile,item,assigned_to,due_date,category,source,sys_created_on,sys_updated_on,closed_at
  limit: 100

Using REST API:

GET /api/now/table/sn_grc_issue?sysparm_query=active=true^ORDERBYDESCpriority&sysparm_fields=sys_id,number,short_description,description,state,priority,risk_rating,profile,item,assigned_to,due_date,category,source&sysparm_limit=100

Step 2: Gather Related Risk Context

For each issue, pull associated risk records to understand the risk landscape.

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_risk
  query: profile=[issue_profile_sys_id]^active=true
  fields: sys_id,number,short_description,risk_score,state,treatment,residual_risk,inherent_risk,category,owner
  limit: 50

Using REST API:

GET /api/now/table/sn_grc_risk?sysparm_query=profile=[profile_sys_id]^active=true&sysparm_fields=sys_id,number,short_description,risk_score,state,treatment,residual_risk,inherent_risk,category&sysparm_limit=50

Step 3: Identify Compliance Gaps and Control Failures

Query controls in non-compliant states linked to issue profiles.

Using MCP:

Tool: SN-Query-Table
Parameters:
  table_name: sn_compliance_control
  query: profile=[issue_profile_sys_id]^stateNOT INcompliant,passed^active=true
  fields: sys_id,number,short_description,state,control_objective,owner,effectiveness,test_result,test_date
  limit: 50

Using REST API:

GET /api/now/table/sn_compliance_control?sysparm_query=profile=[profile_sys_id]^stateNOT INcompliant,passed^active=true&sysparm_fields=sys_id,number,short_description,state,control_objective,owner,effectiveness,test_result&sysparm_limit=50

Step 4: Generate Executive Summary with Aggregated Metrics

Build a comprehensive summary with statistics and trends:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var summary = {
      generated_date: new GlideDateTime().toString(),
      issue_overview: { total_open: 0, critical: 0, high: 0, medium: 0, low: 0, overdue: 0 },
      by_category: {},
      by_source: {},
      risk_context: { avg_risk_score: 0, max_risk_score: 0, total_risks: 0 },
      compliance_gaps: { failing_controls: 0, untested_controls: 0 },
      aging: { under_30_days: 0, days_30_to_60: 0, days_60_to_90: 0, over_90_days: 0 },
      resolution_rate: { closed_last_30: 0, opened_last_30: 0 }
    };

    var now = new GlideDateTime();
    var gr = new GlideRecord('sn_grc_issue');
    gr.addQuery('active', true);
    gr.query();

    var riskScores = [];
    while (gr.next()) {
      summary.issue_overview.total_open++;

      // Priority distribution
      var pri = gr.priority.toString();
      if (pri == '1') summary.issue_overview.critical++;
      else if (pri == '2') summary.issue_overview.high++;
      else if (pri == '3') summary.issue_overview.medium++;
      else summary.issue_overview.low++;

      // Overdue check
      if (gr.due_date.toString() && new GlideDateTime(gr.due_date.toString()).compareTo(now) < 0) {
        summary.issue_overview.overdue++;
      }

      // Category breakdown
      var cat = gr.category.getDisplayValue() || 'Uncategorized';
      summary.by_category[cat] = (summary.by_category[cat] || 0) + 1;

      // Source breakdown
      var src = gr.source.getDisplayValue() || 'Manual';
      summary.by_source[src] = (summary.by_source[src] || 0) + 1;

      // Aging analysis
      var created = new GlideDateTime(gr.sys_created_on.toString());
      var ageDays = gs.dateDiff(created.toString(), now.toString(), true);
      if (ageDays < 30) summary.aging.under_30_days++;
      else if (ageDays < 60) summary.aging.days_30_to_60++;
      else if (ageDays < 90) summary.aging.days_60_to_90++;
      else summary.aging.over_90_days++;

      // Collect risk scores from related risks
      var risk = new GlideRecord('sn_grc_risk');
      risk.addQuery('profile', gr.profile.toString());
      risk.addQuery('active', true);
      risk.query();
      while (risk.next()) {
        var score = parseInt(risk.risk_score.toString()) || 0;
        riskScores.push(score);
        summary.risk_context.total_risks++;
        if (score > summary.risk_context.max_risk_score) summary.risk_context.max_risk_score = score;
      }
    }

    // Average risk score
    if (riskScores.length > 0) {
      var total = 0;
      for (var i = 0; i < riskScores.length; i++) total += riskScores[i];
      summary.risk_context.avg_risk_score = Math.round(total / riskScores.length);
    }

    // Compliance gap count
    var ctrl = new GlideAggregate('sn_compliance_control');
    ctrl.addQuery('active', true);
    ctrl.addQuery('state', 'NOT IN', 'compliant,passed');
    ctrl.addAggregate('COUNT');
    ctrl.query();
    if (ctrl.next()) summary.compliance_gaps.failing_controls = parseInt(ctrl.getAggregate('COUNT'));

    // Resolution rate (last 30 days)
    var closed = new GlideAggregate('sn_grc_issue');
    closed.addQuery('closed_at', '>=', gs.daysAgo(30));
    closed.addAggregate('COUNT');
    closed.query();
    if (closed.next()) summary.resolution_rate.closed_last_30 = parseInt(closed.getAggregate('COUNT'));

    var opened = new GlideAggregate('sn_grc_issue');
    opened.addQuery('sys_created_on', '>=', gs.daysAgo(30));
    opened.addAggregate('COUNT');
    opened.query();
    if (opened.next()) summary.resolution_rate.opened_last_30 = parseInt(opened.getAggregate('COUNT'));

    gs.info('EXECUTIVE GRC ISSUE SUMMARY:\n' + JSON.stringify(summary, null, 2));
  description: "GRC: Generate executive summary of open issues with metrics"

Step 5: Generate Individual Issue Detail Summary

Produce a deep-dive summary for a specific issue:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var issueNumber = 'ISSUE0001234'; // Replace with target issue
    var gr = new GlideRecord('sn_grc_issue');
    gr.addQuery('number', issueNumber);
    gr.query();

    if (gr.next()) {
      var detail = {
        issue: {
          number: gr.number.toString(),
          title: gr.short_description.toString(),
          description: gr.description.toString(),
          state: gr.state.getDisplayValue(),
          priority: gr.priority.getDisplayValue(),
          risk_rating: gr.risk_rating.toString(),
          category: gr.category.getDisplayValue(),
          source: gr.source.getDisplayValue(),
          assigned_to: gr.assigned_to.getDisplayValue(),
          profile: gr.profile.getDisplayValue(),
          created: gr.sys_created_on.toString(),
          due_date: gr.due_date.toString()
        },
        related_risks: [],
        affected_controls: [],
        policy_references: [],
        business_impact: ''
      };

      // Related risks
      var risk = new GlideRecord('sn_grc_risk');
      risk.addQuery('profile', gr.profile.toString());
      risk.addQuery('active', true);
      risk.query();
      while (risk.next()) {
        detail.related_risks.push({
          number: risk.number.toString(),
          title: risk.short_description.toString(),
          inherent_risk: risk.inherent_risk.toString(),
          residual_risk: risk.residual_risk.toString(),
          treatment: risk.treatment.getDisplayValue()
        });
      }

      // Affected controls
      var ctrl = new GlideRecord('sn_compliance_control');
      ctrl.addQuery('profile', gr.profile.toString());
      ctrl.query();
      while (ctrl.next()) {
        detail.affected_controls.push({
          number: ctrl.number.toString(),
          title: ctrl.short_description.toString(),
          state: ctrl.state.getDisplayValue(),
          effectiveness: ctrl.effectiveness.getDisplayValue(),
          owner: ctrl.owner.getDisplayValue()
        });
      }

      // Policy references via profile
      var policy = new GlideRecord('sn_compliance_policy');
      policy.addQuery('active', true);
      policy.query();
      while (policy.next()) {
        detail.policy_references.push({
          number: policy.number.toString(),
          title: policy.short_description.toString(),
          state: policy.state.getDisplayValue()
        });
      }

      // Determine business impact narrative
      var maxRisk = 0;
      for (var i = 0; i < detail.related_risks.length; i++) {
        var rs = parseInt(detail.related_risks[i].residual_risk) || 0;
        if (rs > maxRisk) maxRisk = rs;
      }
      var failingCtrls = detail.affected_controls.filter(function(c) {
        return c.state !== 'Compliant' && c.state !== 'Passed';
      }).length;

      if (maxRisk >= 80 || failingCtrls >= 3) {
        detail.business_impact = 'HIGH - Significant compliance exposure with elevated residual risk and multiple control failures';
      } else if (maxRisk >= 50 || failingCtrls >= 1) {
        detail.business_impact = 'MEDIUM - Moderate compliance concern requiring timely remediation';
      } else {
        detail.business_impact = 'LOW - Minor finding with limited business impact';
      }

      gs.info('ISSUE DETAIL SUMMARY:\n' + JSON.stringify(detail, null, 2));
    }
  description: "GRC: Generate detailed summary for a specific issue"

Step 6: Generate Audit Committee Narrative

Using an encoded full-text query to find issues by theme:

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_issue
  query: priorityIN1,2^sys_created_on>=javascript:gs.monthsAgoStart(3)^123TEXTQUERY321=data privacy access controls
  limit: 25

Produce a formatted narrative:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    var narrative = [];
    narrative.push('=== GRC ISSUE SUMMARY FOR AUDIT COMMITTEE ===');
    narrative.push('Report Date: ' + new GlideDateTime().getDisplayValue());
    narrative.push('');

    // Open issue statistics
    var agg = new GlideAggregate('sn_grc_issue');
    agg.addQuery('active', true);
    agg.addAggregate('COUNT');
    agg.groupBy('priority');
    agg.query();

    narrative.push('OPEN ISSUES BY PRIORITY:');
    while (agg.next()) {
      narrative.push('  Priority ' + agg.priority.getDisplayValue() + ': ' + agg.getAggregate('COUNT') + ' issues');
    }
    narrative.push('');

    // Overdue issues
    var overdue = new GlideAggregate('sn_grc_issue');
    overdue.addQuery('active', true);
    overdue.addQuery('due_date', '<', new GlideDateTime().toString());
    overdue.addAggregate('COUNT');
    overdue.query();
    if (overdue.next()) {
      narrative.push('OVERDUE ISSUES: ' + overdue.getAggregate('COUNT'));
    }
    narrative.push('');

    // Top 5 critical issues
    narrative.push('TOP CRITICAL ISSUES:');
    var top = new GlideRecord('sn_grc_issue');
    top.addQuery('active', true);
    top.addQuery('priority', 'IN', '1,2');
    top.orderByDesc('risk_rating');
    top.setLimit(5);
    top.query();
    var idx = 1;
    while (top.next()) {
      narrative.push('  ' + idx + '. ' + top.number + ' - ' + top.short_description);
      narrative.push('     Risk Rating: ' + top.risk_rating + ' | Due: ' + top.due_date.getDisplayValue());
      idx++;
    }

    gs.info(narrative.join('\n'));
  description: "GRC: Generate audit committee narrative report"

Tool Usage

OperationMCP ToolREST Endpoint
Query IssuesSN-Query-TableGET /api/now/table/sn_grc_issue
Search IssuesSN-Natural-Language-SearchN/A
Query RisksSN-Query-TableGET /api/now/table/sn_grc_risk
Query ControlsSN-Query-TableGET /api/now/table/sn_compliance_control
Aggregate AnalysisSN-Execute-Background-ScriptPOST /api/now/table/sys_trigger
Schema DiscoverySN-Discover-Table-SchemaGET /api/now/table/sys_dictionary

Best Practices

  • Audience Awareness: Tailor summary depth to the audience -- executives need high-level metrics, compliance teams need control-level detail
  • Trend Context: Always include trend data (month-over-month, quarter-over-quarter) to show trajectory
  • Risk Correlation: Link issues to their underlying risks to show root cause patterns
  • Aging Visibility: Highlight overdue issues and aging buckets prominently in executive summaries
  • Regulatory Mapping: Map issues to specific regulatory requirements or citations when available
  • Actionable Language: Summaries should include recommended next steps, not just status descriptions
  • Data Freshness: Include the report generation timestamp and data currency disclaimer

Troubleshooting

Empty Risk Context for Issues

Symptom: Issues return no related risks when queried by profile Cause: Issues may not be linked to a GRC profile, or the profile relationship uses a different field Solution:

Tool: SN-Discover-Table-Schema
Parameters:
  table_name: sn_grc_issue

Check for alternative relationship fields such as item, content, or custom reference fields linking to risk records.

Category Field Returns Empty

Symptom: Issue category breakdowns show all items as "Uncategorized" Cause: The category field may not be populated or may use a different field name Solution: Query the sys_choice table for sn_grc_issue to verify available category values and check if a custom field like issue_type is used instead.

Aggregate Queries Timeout

Symptom: Background scripts with GlideAggregate timeout on large datasets Cause: Large volume of issue records without proper indexing Solution: Add date range filters to limit the dataset, or use setLimit() on detail queries within aggregation loops.

Examples

Example 1: Monthly Compliance Posture Summary

Scenario: Compliance manager needs a monthly overview for the risk committee

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_issue
  query: active=true^priorityIN1,2^sys_created_on>=javascript:gs.beginningOfThisMonth()^ORsys_updated_on>=javascript:gs.beginningOfThisMonth()
  limit: 50

Generated Summary:

  • Total Open Issues: 47 (up from 42 last month)
  • Critical/High: 12 issues requiring immediate attention
  • Overdue: 8 issues past their due date (17% overdue rate)
  • Top Category: Access Control (15 issues, 32%)
  • Average Risk Score: 67/100 across related risk records
  • Control Failures: 9 controls in non-compliant state
  • Recommendation: Escalate 3 overdue critical issues to executive sponsor; schedule control re-testing for Q2

Example 2: Single Issue Deep-Dive for Audit

Scenario: External auditor requests detailed context on a specific finding

Tool: SN-Query-Table
Parameters:
  table_name: sn_grc_issue
  query: number=ISSUE0002341
  fields: sys_id,number,short_description,description,state,priority,risk_rating,profile,item,assigned_to,due_date,category,source,work_notes,sys_created_on
  limit: 1

Generated Detail:

  • Issue: ISSUE0002341 - Inadequate encryption for data at rest
  • Source: External Audit Finding
  • Risk Rating: 4 (Critical)
  • Related Risks: 2 risks with average residual score of 75
  • Affected Controls: CTRL0001892 (Failed), CTRL0001893 (Not Tested)
  • Policy Reference: POL0000145 - Data Protection Policy
  • Business Impact: HIGH - Regulatory exposure under GDPR Article 32; potential fine up to 4% annual revenue
  • Days Open: 45 (15 days past due date)

Related Skills

  • grc/issue-action-plan - Generate remediation action plans for summarized issues
  • grc/risk-assessment-summarization - Deeper risk analysis behind issues
  • grc/control-objective-management - Manage controls linked to issues
  • grc/regulatory-alert-analysis - Identify regulatory drivers for issues
  • security/audit-compliance - Audit trail and compliance reporting

References

Signals

GitHub stars
37
Forks
13
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
issue-summarization
Source
github.com/happy-technologies-llc/happy-platform-skills