Task: workarena.servicenow.create-change-request
Goal: Create a new change request with a value of "Low" for field "Risk", a value of "Power down switchReprovision existing Cisco 2900 switchValidate connectivity from all private networks" for field "Backout plan", a value of "" for field "Service", a value of "The existing Cisco 2900 is being decommissioned and replaced with a Cisco 4500 that supports additional services and is still supported by Cisco." for field "Description", a value of "IP-Router-3" for field "Configuration item", a value of "calycled overcommend wileless cretinoid rotch" for field "Test plan", a value of "Network" for field "Assignment group", a value of "Deploy new Cisco Catalyst 4500" for field "Short description", a value of "2 - Medium" for field "Impact", a value of "" for field "Service offering", a value of "Move new Cisco 4500 from test network to production networkProvision production configuration to switchTest connectivity from all existing private networksRemove existing Cisco 2900 from network" for field "Implementation plan", a value of "CHG0000039" for field "Number", a value of "mannitose crosspoint debus permeable williamsite" for field "Justification", a value of "Network" for field "Category", a value of "forefin genapper rainily drumheads comfy" for field "Close notes", and a value of "Lyngbyeae impetration moping leonines unethereal" for field "Risk and impact analysis".
Task configuration: {'table_name': 'change_request', 'task_fields': ['risk', 'backout_plan', 'business_service', 'description', 'cmdb_ci', 'test_plan', 'assignment_group', 'short_description', 'impact', 'service_offering', 'implementation_plan', 'number', 'justification', 'category', 'close_notes', 'risk_impact_analysis'], 'template_record': {'active': 'true', 'activity_due': 'UNKNOWN', 'additional_assignee_list': '', 'approval': 'Requested', 'approval_history': '2023-10-08 06:07:19 - System Administrator (Approval history)\nGroup approval for Network approved by user David Loo (Technical approvals).\n\n', 'approval_set': '', 'assigned_to': {'display_value': 'David Loo', 'link': 'https://dev215901.service-now.com/api/now/table/sys_user/5137153cc611227c000bbd1bd8cd2007'}, 'assignment_group': 'Network', 'backout_plan': 'Power down switchReprovision existing Cisco 2900 switchValidate connectivity from all private networks', 'business_duration': '', 'business_service': '', 'cab_date_time': '', 'cab_delegate': '', 'cab_recommendation': '', 'cab_required': 'false', 'calendar_duration': '', 'category': 'Network', 'change_plan': '', 'chg_model': '', 'close_code': 'Successful with issues', 'close_notes': 'forefin genapper rainily drumheads comfy', 'closed_at': '', 'closed_by': '', 'cmdb_ci': 'IP-Router-3', 'comments': '', 'comments_and_work_notes': '', 'company': '', 'conflict_last_run': '2023-10-09 01:02:55', 'conflict_status': 'Conflict', 'contact_type': None, 'contract': '', 'correlation_display': '', 'correlation_id': '', 'delivery_plan': '', 'delivery_task': '', 'description': 'The existing Cisco 2900 is being decommissioned and replaced with a Cisco 4500 that supports additional services and is still supported by Cisco.', 'due_date': '', 'end_date': '2023-10-24 09:30:00', 'escalation': 'Normal', 'expected_start': '', 'follow_up': '', 'group_list': '', 'impact': '2 - Medium', 'implementation_plan': 'Move new Cisco 4500 from test network to production networkProvision production configuration to switchTest connectivity from all existing private networksRemove existing Cisco 2900 from network', 'justification': 'mannitose crosspoint debus permeable williamsite', 'knowledge': 'false', 'location': '', 'made_sla': 'true', 'number': 'CHG0000039', 'on_hold': 'false', 'on_hold_reason': '', 'on_hold_task': '', 'opened_at': '2023-10-06 01:21:31', 'opened_by': {'display_value': 'System Administrator', 'link': 'https://dev215901.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441'}, 'order': '', 'outside_maintenance_schedule': 'false', 'parent': '', 'phase': None, 'phase_state': 'Open', 'priority': '2 - High', 'production_system': 'false', 'reason': None, 'reassignment_count': '0', 'requested_by': {'display_value': 'System Administrator', 'link': 'https://dev215901.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441'}, 'requested_by_date': '', 'review_comments': '', 'review_date': '', 'review_status': '', 'risk': 'Low', 'risk_impact_analysis': 'Lyngbyeae impetration moping leonines unethereal', 'route_reason': '', 'scope': 'Medium', 'service_offering': '', 'short_description': 'Deploy new Cisco Catalyst 4500', 'sla_due': 'UNKNOWN', 'start_date': '2023-10-24 07:30:00', 'state': 'Authorize', 'std_change_producer_version': '', 'sys_class_name': 'Change Request', 'sys_created_by': 'admin', 'sys_created_on': '2023-10-06 01:21:31', 'sys_domain': {'display_value': 'global', 'link': 'https://dev215901.service-now.com/api/now/table/sys_user_group/global'}, 'sys_domain_path': '/', 'sys_id': '0dcdb152db252200a6a2b31be0b8f54f', 'sys_mod_count': '16', 'sys_tags': '', 'sys_updated_by': 'admin', 'sys_updated_on': '2023-10-09 01:02:55', 'task_effective_number': 'CHG0000039', 'test_plan': 'calycled overcommend wileless cretinoid rotch', 'time_worked': '', 'type': 'Normal', 'unauthorized': 'false', 'universal_request': '', 'upon_approval': 'Proceed to Next Task', 'upon_reject': 'Cancel all future Tasks', 'urgency': '3 - Low', 'user_input': '', 'watch_list': '', 'work_end': '', 'work_notes': '', 'work_notes_list': '', 'work_start': '2023-10-08 02:48:23'}}

Validation code:
import json
import os
import re
import urllib.parse as parse
from typing import Any, Dict, List

import requests


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Validate that a record was created with expected field values.
    Returns 1 on success, else 0.

    Expected config keys:
      - task_fields (list[str])                  # fields to validate
      - template_record (dict)                   # expected display values for those fields
    """

    # Check whether we have the necessary information to validate
    print(config)
    if "table_name" not in config or "task_fields" not in config or "template_record" not in config:
        raise ValueError("Target must contain 'table_name', 'task_fields' and 'template_record'")

    # Check whether we have messages
    if not messages:
        raise ValueError("Messages must not be empty")

    # Extract last message content
    content = messages[-1].get("content", "")

    # Extract all URLs — markdown links first, then bare URLs
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)
    if not urls:
        return 0

    def extract_sys_id(url: str) -> str | None:
        """
        Return the 32-hex sys_id from a ServiceNow URL, or None if not found.
        Handles encoded target URLs like .../params/target/incident.do%3Fsys_id%3D<id>
        """
        # Unquote repeatedly (some SN URLs are doubly-encoded)
        for _ in range(3):
            new = parse.unquote(url)
            if new == url:
                break
            url = new

        m = re.search(r"(?:[?&])sys_id=([0-9a-f]{32})", url, re.IGNORECASE)
        return m.group(1) if m else None

    sys_id = None
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        sys_id = extract_sys_id(current_url)
        if sys_id:
            break
    if not sys_id:
        return 0

    # Query instance to get record
    response = requests.request(
        method="GET",
        url=os.getenv("SERVICENOW_INSTANCE_URL", "").rstrip("/") + f"/api/now/table/{config['table_name']}",
        headers={
            "Content-Type": "application/json",
            "Accept": "application/json",
            **json.loads(os.getenv("SERVICENOW_EXTRA_HTTP_HEADERS", "{}")),
        },
        params={
            "sysparm_query": f"sys_id={sys_id}",
            "sysparm_display_value": "true",
            "sysparm_fields": ",".join(config["task_fields"]),
        },
    )
    response.raise_for_status()
    if type(response) == dict:
        record = response
    else:
        record = response.json()

    if "result" in record:
        record = record["result"]

    if isinstance(record, list):
        if len(record) == 0:
            return 0
        record = record[0]

    # Check that the record matches the expected values
    for f in config["task_fields"]:
        val = record.get(f)
        # Handle nested display_value dicts from the API
        if isinstance(val, dict) and "display_value" in val:
            val = val["display_value"]
        if val is None:
            return 0
        if val != config["template_record"][f]:
            return 0
    return 1