// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
 * @fileoverview Types and parsing functions for updater event history.
 *
 * Event history is logged by the updater as a series of structured events
 * formatted as JSON blobs. This file provides tools to parse these events and
 * organize them into a more useful structure.
 *
 * See //docs/updater/history_log.md for details on the serialization format.
 */
import { loadTimeData } from './i18n_setup.js';
// ---------------------------------------------------------------------------
// Common Types
// ---------------------------------------------------------------------------
/**
 * A list of event types which are understood by this library.
 */
export const EVENT_TYPES = [
    'INSTALL',
    'UNINSTALL',
    'QUALIFY',
    'ACTIVATE',
    'PERSISTED_DATA',
    'POST_REQUEST',
    'LOAD_POLICY',
    'UPDATE',
    'UPDATER_PROCESS',
    'APP_COMMAND',
];
const BOUNDS = ['START', 'END', 'INSTANT'];
const UPDATE_PRIORITIES = ['BACKGROUND', 'FOREGROUND'];
export const SCOPES = ['USER', 'SYSTEM'];
/**
 * Returns the localized title of an event type.
 */
export function localizeEventType(eventType) {
    return loadTimeData.getString(`eventType${eventType}`);
}
export const COMMON_UPDATE_OUTCOMES = ['UPDATED', 'NO_UPDATE', 'UPDATE_ERROR'];
/**
 * Returns the localized title of a common update outcome.
 */
export function localizeUpdateOutcome(outcome) {
    return loadTimeData.getString(`updateOutcome${outcome}`);
}
/**
 * Returns the localized title of a scope.
 */
export function localizeScope(scope) {
    return loadTimeData.getString(scope === 'SYSTEM' ? 'scopeSystem' : 'scopeUser');
}
// ---------------------------------------------------------------------------
// Type Guards
// ---------------------------------------------------------------------------
function isInstallStart(event) {
    return event.eventType === 'INSTALL' && event.bound === 'START';
}
function isInstallEnd(event) {
    return event.eventType === 'INSTALL' && event.bound === 'END';
}
function isUninstallStart(event) {
    return event.eventType === 'UNINSTALL' && event.bound === 'START';
}
function isUninstallEnd(event) {
    return event.eventType === 'UNINSTALL' && event.bound === 'END';
}
function isUpdateStart(event) {
    return event.eventType === 'UPDATE' && event.bound === 'START';
}
function isUpdateEnd(event) {
    return event.eventType === 'UPDATE' && event.bound === 'END';
}
function isPersistedData(event) {
    return event.eventType === 'PERSISTED_DATA' && event.bound === 'INSTANT';
}
function isQualifyStart(event) {
    return event.eventType === 'QUALIFY' && event.bound === 'START';
}
function isQualifyEnd(event) {
    return event.eventType === 'QUALIFY' && event.bound === 'END';
}
function isActivateStart(event) {
    return event.eventType === 'ACTIVATE' && event.bound === 'START';
}
function isActivateEnd(event) {
    return event.eventType === 'ACTIVATE' && event.bound === 'END';
}
function isPostRequestStart(event) {
    return event.eventType === 'POST_REQUEST' && event.bound === 'START';
}
function isPostRequestEnd(event) {
    return event.eventType === 'POST_REQUEST' && event.bound === 'END';
}
function isLoadPolicyStart(event) {
    return event.eventType === 'LOAD_POLICY' && event.bound === 'START';
}
function isLoadPolicyEnd(event) {
    return event.eventType === 'LOAD_POLICY' && event.bound === 'END';
}
function isUpdaterProcessStart(event) {
    return event.eventType === 'UPDATER_PROCESS' && event.bound === 'START';
}
function isUpdaterProcessEnd(event) {
    return event.eventType === 'UPDATER_PROCESS' && event.bound === 'END';
}
function isAppCommandStart(event) {
    return event.eventType === 'APP_COMMAND' && event.bound === 'START';
}
function isAppCommandEnd(event) {
    return event.eventType === 'APP_COMMAND' && event.bound === 'END';
}
/**
 * Type guard for MergedHistoryEvent.
 */
export function isMergedHistoryEvent(event) {
    return 'startEvent' in event;
}
// ---------------------------------------------------------------------------
// Parsing
// ---------------------------------------------------------------------------
/**
 * An error indicating that a message could not be parsed as an event.
 */
class ParseError extends Error {
}
/**
 * A wrapper for parsing functions that throws an error if the field is missing.
 */
function required(parser, message, fieldName) {
    const parsed = parser(message, fieldName);
    if (parsed === undefined) {
        throw new ParseError(`Message missing required field '${fieldName}'`);
    }
    return parsed;
}
/**
 * Parses an integer field from a message. The field may be a natural integer or
 * the string representation an integer.
 */
function parseInteger(message, fieldName) {
    const value = message[fieldName];
    if (value === undefined || value === null) {
        return undefined;
    }
    let num;
    switch (typeof value) {
        case 'number':
            num = value;
            break;
        case 'string':
            num = Number(value);
            break;
        default:
            throw new ParseError(`Message has field '${fieldName}' with unexpected type '${typeof value}', expected 'number' or 'string'`);
    }
    if (!Number.isInteger(num)) {
        throw new ParseError(`Message has field '${fieldName}' with a numeric value that is not an integer.`);
    }
    return num;
}
/**
 * Parses a string field from a message.
 */
function parseString(message, fieldName) {
    const value = message[fieldName];
    if (value === undefined || value === null) {
        return undefined;
    }
    if (typeof value !== 'string') {
        throw new ParseError(`Message has field ${fieldName} with unexpected type '${typeof value}', expected 'string'`);
    }
    return value;
}
/**
 * Parses a boolean field from a message.
 */
function parseBoolean(message, fieldName) {
    const value = message[fieldName];
    if (value === undefined || value === null) {
        return undefined;
    }
    if (typeof value !== 'boolean') {
        throw new ParseError(`Message has field ${fieldName} with unexpected type '${typeof value}', expected 'boolean'`);
    }
    return value;
}
/**
 * Parses an object field from a message.
 */
function parseObject(message, fieldName) {
    const value = message[fieldName];
    if (value === undefined || value === null) {
        return undefined;
    }
    if (typeof value !== 'object' || Array.isArray(value)) {
        throw new ParseError(`Message has field ${fieldName} with unexpected type '${typeof value}', expected 'object'`);
    }
    return value;
}
/**
 * Parses an EventType field from a message.
 */
function parseEventType(message, fieldName) {
    const eventType = parseString(message, fieldName);
    if (eventType === undefined) {
        return undefined;
    }
    if (!EVENT_TYPES.includes(eventType)) {
        throw new ParseError(`Message contains unknown eventType: ${eventType}`);
    }
    return eventType;
}
/**
 * Parses a Bound field from a message.
 */
function parseBound(message, fieldName) {
    const bound = parseString(message, fieldName) ?? 'INSTANT';
    if (!BOUNDS.includes(bound)) {
        throw new ParseError(`Message contains unknown bound: ${bound}`);
    }
    return bound;
}
/**
 * Parses an UpdaterError from a message.
 */
function parseUpdaterError(message) {
    const category = required(parseInteger, message, 'category');
    const code = required(parseInteger, message, 'code');
    const extracode1 = required(parseInteger, message, 'extracode1');
    return { category, code, extracode1 };
}
/**
 * Parses a list of UpdaterErrors from a message.
 */
function parseUpdaterErrors(message) {
    const errors = message['errors'];
    // An absent error list is equivalent to an empty error list.
    if (errors === undefined || errors === null) {
        return [];
    }
    if (!Array.isArray(errors)) {
        throw new ParseError(`Message has field 'errors' of unexpected non-array type '${typeof errors}'.`);
    }
    const parsedErrors = [];
    for (const errorItem of errors) {
        if (typeof errorItem !== 'object' || errorItem === null) {
            throw new ParseError(`Message has field 'errors' containing an element of unexpected type '${typeof errorItem}', expected 'object'.`);
        }
        if (Array.isArray(errorItem)) {
            throw new ParseError(`Message has field 'errors' of unexpected array type.`);
        }
        parsedErrors.push(parseUpdaterError(errorItem));
    }
    return parsedErrors;
}
/**
 * Parses a UpdateState from a message.
 */
function parseUpdateState(message) {
    const deviceUptime = parseInteger(message, 'deviceUptime');
    const state = required(parseString, message, 'state');
    return { deviceUptime, state };
}
/**
 * Parses a list of UpdateStates from a message.
 */
function parseUpdateStates(message, fieldName) {
    const updateStates = message[fieldName];
    if (updateStates === undefined || updateStates === null) {
        return [];
    }
    if (!Array.isArray(updateStates)) {
        throw new ParseError(`Message has field '${fieldName}' of unexpected non-array type '${typeof updateStates}'.`);
    }
    const parsedUpdateStates = [];
    for (const updateStateItem of updateStates) {
        if (typeof updateStateItem !== 'object' || updateStateItem === null) {
            throw new ParseError(`Message has field '${fieldName}' containing an element of unexpected type '${typeof updateStateItem}', expected 'object'.`);
        }
        if (Array.isArray(updateStateItem)) {
            throw new ParseError(`Message has field '${fieldName}' of unexpected array type.`);
        }
        parsedUpdateStates.push(parseUpdateState(updateStateItem));
    }
    return parsedUpdateStates;
}
/**
 * Parses a RegisteredApp from a message.
 */
function parseRegisteredApp(message) {
    const appId = required(parseString, message, 'appId');
    const version = required(parseString, message, 'version');
    const cohort = parseString(message, 'cohort');
    const brandCode = parseString(message, 'brandCode');
    return { appId, version, cohort, brandCode };
}
/**
 * Parses a list of RegisteredApps from a message.
 */
function parseRegisteredApps(message, fieldName) {
    const registeredApps = message[fieldName];
    if (registeredApps === undefined || registeredApps === null) {
        return [];
    }
    if (!Array.isArray(registeredApps)) {
        throw new ParseError(`Message has field '${fieldName}' of unexpected non-array type '${typeof registeredApps}'.`);
    }
    const parsedRegisteredApps = [];
    for (const appItem of registeredApps) {
        if (typeof appItem !== 'object' || appItem === null) {
            throw new ParseError(`Message has field '${fieldName}' containing an element of unexpected type '${typeof appItem}', expected 'object'.`);
        }
        if (Array.isArray(appItem)) {
            throw new ParseError(`Message has field '${fieldName}' of unexpected array type.`);
        }
        parsedRegisteredApps.push(parseRegisteredApp(appItem));
    }
    return parsedRegisteredApps;
}
/**
 * Parses an UpdatePriority field from a message.
 */
function parseUpdatePriority(message, fieldName) {
    const priority = parseString(message, fieldName);
    if (priority === undefined) {
        return undefined;
    }
    if (!UPDATE_PRIORITIES.includes(priority)) {
        throw new ParseError(`Message contains unknown update priority: ${priority}`);
    }
    return priority;
}
/**
 * Parses a Scope field from a message.
 */
function parseScope(message, fieldName) {
    const scope = parseString(message, fieldName);
    if (scope === undefined) {
        return undefined;
    }
    if (!SCOPES.includes(scope)) {
        throw new ParseError(`Message contains unknown scope: ${scope}`);
    }
    return scope;
}
/**
 * Parses PolicyData from a message.
 */
function parsePolicyData(message) {
    const valuesBySource = required((parseObject), message, 'valuesBySource');
    const prevailingSource = required(parseString, message, 'prevailingSource');
    return { valuesBySource, prevailingSource };
}
/**
 * Parses a PolicySet from a message.
 */
export function parsePolicySet(message, fieldName) {
    const policySet = parseObject(message, fieldName);
    if (policySet === undefined) {
        return undefined;
    }
    const policiesByNameMessage = required(parseObject, policySet, 'policiesByName');
    const policiesByName = {};
    for (const key of Object.keys(policiesByNameMessage)) {
        policiesByName[key] = parsePolicyData(required(parseObject, policiesByNameMessage, key));
    }
    const policiesByAppIdMessage = required(parseObject, policySet, 'policiesByAppId');
    const policiesByAppId = {};
    for (const key of Object.keys(policiesByAppIdMessage)) {
        policiesByAppId[key] = {};
        const appPolicies = required(parseObject, policiesByAppIdMessage, key);
        for (const appKey of Object.keys(appPolicies)) {
            policiesByAppId[key][appKey] =
                parsePolicyData(required(parseObject, appPolicies, appKey));
        }
    }
    return { policiesByName, policiesByAppId };
}
// Microseconds between 1601-01-01 and 1970-01-01.
const WINDOWS_TO_UNIX_EPOCH_OFFSET = 11644473600000000n;
/**
 * Parses a Date field from a message. Dates are received as string-encoded
 * integer number of microseconds since Windows epoch.
 */
function parseDate(message, fieldName) {
    const dateStr = parseString(message, fieldName);
    if (dateStr === undefined) {
        return undefined;
    }
    let windowsMicroseconds;
    try {
        windowsMicroseconds = BigInt(dateStr);
    }
    catch (e) {
        throw new ParseError(`Message has field '${fieldName}' with unparsable datetime value '${dateStr}'`);
    }
    const unixMilliseconds = Number((windowsMicroseconds - WINDOWS_TO_UNIX_EPOCH_OFFSET) / 1000n);
    if (!Number.isFinite(unixMilliseconds)) {
        throw new ParseError(`Message has field '${fieldName}' with unparsable datetime value '${dateStr}'`);
    }
    return new Date(unixMilliseconds);
}
/**
 * Parses a BaseEvent from a message.
 */
function parseBaseEvent(message) {
    const eventType = required(parseEventType, message, 'eventType');
    const eventId = required(parseString, message, 'eventId');
    const deviceUptime = required(parseInteger, message, 'deviceUptime');
    const pid = required(parseInteger, message, 'pid');
    const processToken = required(parseString, message, 'processToken');
    const bound = parseBound(message, 'bound');
    const errors = parseUpdaterErrors(message);
    return { eventType, eventId, deviceUptime, pid, processToken, bound, errors };
}
function parseInstallStartEvent(base, message) {
    const appId = required(parseString, message, 'appId');
    return { ...base, appId };
}
function parseInstallEndEvent(base, message) {
    const version = required(parseString, message, 'version');
    return { ...base, version };
}
function parseUninstallStartEvent(base, message) {
    const appId = required(parseString, message, 'appId');
    const version = required(parseString, message, 'version');
    const reason = required(parseString, message, 'reason');
    return { ...base, appId, version, reason };
}
function parseUninstallEndEvent(base, _) {
    return { ...base };
}
function parseUpdateStartEvent(base, message) {
    const appId = parseString(message, 'appId');
    const priority = parseUpdatePriority(message, 'priority');
    return { ...base, appId, priority };
}
function parseUpdateEndEvent(base, message) {
    const nextVersion = parseString(message, 'nextVersion');
    const updateStates = parseUpdateStates(message, 'updateStates');
    const result = parseString(message, 'result');
    return { ...base, nextVersion, updateStates, result };
}
function parseQualifyStartEvent(base, _) {
    return { ...base };
}
function parseQualifyEndEvent(base, message) {
    const qualified = parseBoolean(message, 'qualified');
    return { ...base, qualified };
}
function parseActivateStartEvent(base, _) {
    return { ...base };
}
function parseActivateEndEvent(base, message) {
    const activated = parseBoolean(message, 'activated');
    return { ...base, activated };
}
function parsePostRequestStartEvent(base, message) {
    const request = required(parseString, message, 'request');
    return { ...base, request };
}
function parsePostRequestEndEvent(base, message) {
    const response = parseString(message, 'response');
    return { ...base, response };
}
function parseLoadPolicyStartEvent(base, _) {
    return { ...base };
}
function parseLoadPolicyEndEvent(base, message) {
    const policySet = parsePolicySet(message, 'policySet');
    return { ...base, policySet };
}
function parseUpdaterProcessStartEvent(base, message) {
    const commandLine = parseString(message, 'commandLine');
    const timestamp = parseDate(message, 'timestamp');
    const updaterVersion = parseString(message, 'updaterVersion');
    const scope = parseScope(message, 'scope');
    const osPlatform = parseString(message, 'osPlatform');
    const osVersion = parseString(message, 'osVersion');
    const osArchitecture = parseString(message, 'osArchitecture');
    const updaterArchitecture = parseString(message, 'updaterArchitecture');
    const parentPid = parseInteger(message, 'parentPid');
    return {
        ...base,
        commandLine,
        timestamp,
        updaterVersion,
        scope,
        osPlatform,
        osVersion,
        osArchitecture,
        updaterArchitecture,
        parentPid,
    };
}
function parseUpdaterProcessEndEvent(base, message) {
    const exitCode = parseInteger(message, 'exitCode');
    return { ...base, exitCode };
}
function parseAppCommandStartEvent(base, message) {
    const appId = required(parseString, message, 'appId');
    const commandLine = parseString(message, 'commandLine');
    return { ...base, appId, commandLine };
}
function parseAppCommandEndEvent(base, message) {
    const exitCode = parseInteger(message, 'exitCode');
    const output = parseString(message, 'output');
    return { ...base, exitCode, output };
}
function parsePersistedDataEvent(base, message) {
    const eulaRequired = required(parseBoolean, message, 'eulaRequired');
    const lastChecked = parseDate(message, 'lastChecked');
    const lastStarted = parseDate(message, 'lastStarted');
    const registeredApps = parseRegisteredApps(message, 'registeredApps');
    return { ...base, eulaRequired, lastChecked, lastStarted, registeredApps };
}
/**
 * Parses a single event from a message.
 */
export function parseEvent(message) {
    const base = parseBaseEvent(message);
    if (isInstallStart(base)) {
        return parseInstallStartEvent(base, message);
    }
    if (isInstallEnd(base)) {
        return parseInstallEndEvent(base, message);
    }
    if (isUninstallStart(base)) {
        return parseUninstallStartEvent(base, message);
    }
    if (isUninstallEnd(base)) {
        return parseUninstallEndEvent(base, message);
    }
    if (isUpdateStart(base)) {
        return parseUpdateStartEvent(base, message);
    }
    if (isUpdateEnd(base)) {
        return parseUpdateEndEvent(base, message);
    }
    if (isQualifyStart(base)) {
        return parseQualifyStartEvent(base, message);
    }
    if (isQualifyEnd(base)) {
        return parseQualifyEndEvent(base, message);
    }
    if (isActivateStart(base)) {
        return parseActivateStartEvent(base, message);
    }
    if (isActivateEnd(base)) {
        return parseActivateEndEvent(base, message);
    }
    if (isPostRequestStart(base)) {
        return parsePostRequestStartEvent(base, message);
    }
    if (isPostRequestEnd(base)) {
        return parsePostRequestEndEvent(base, message);
    }
    if (isLoadPolicyStart(base)) {
        return parseLoadPolicyStartEvent(base, message);
    }
    if (isLoadPolicyEnd(base)) {
        return parseLoadPolicyEndEvent(base, message);
    }
    if (isUpdaterProcessStart(base)) {
        return parseUpdaterProcessStartEvent(base, message);
    }
    if (isUpdaterProcessEnd(base)) {
        return parseUpdaterProcessEndEvent(base, message);
    }
    if (isAppCommandStart(base)) {
        return parseAppCommandStartEvent(base, message);
    }
    if (isAppCommandEnd(base)) {
        return parseAppCommandEndEvent(base, message);
    }
    if (isPersistedData(base)) {
        return parsePersistedDataEvent(base, message);
    }
    throw new ParseError(`No parser implemented for ${base.eventType} with bound ${base.bound}`);
}
/**
 * Parses a list of events from a list of messages.
 */
export function parseEvents(messages) {
    const valid = [];
    const invalid = [];
    for (const message of messages) {
        try {
            valid.push(parseEvent(message));
        }
        catch (e) {
            if (e instanceof ParseError) {
                console.warn(`Failed to parse message: ${e}. Full message: ${message}`);
                invalid.push(message);
            }
            else {
                throw e;
            }
        }
    }
    return { valid, invalid };
}
// ---------------------------------------------------------------------------
// Parsed event processing
// ---------------------------------------------------------------------------
/**
 * Deduplicates events based on event type, event ID, PID, process token, and
 * bound.
 */
export function deduplicateEvents(events) {
    const seen = new Set();
    return events.filter((event) => {
        const key = JSON.stringify([
            event.eventType,
            event.eventId,
            event.pid,
            event.processToken,
            event.bound,
        ]);
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
/**
 * Merges START and END events into pairs
 */
export function mergeEvents(events) {
    const unpaired = [];
    const pairs = new Map();
    for (const event of events) {
        if (event.bound === 'INSTANT') {
            unpaired.push(event);
            continue;
        }
        const key = JSON.stringify([
            event.eventType,
            event.eventId,
            event.pid,
            event.processToken,
        ]);
        const pair = pairs.get(key) ?? {};
        if (event.bound === 'START') {
            if (pair.startEvent) {
                unpaired.push(event);
            }
            else {
                pair.startEvent = event;
            }
        }
        else if (event.bound === 'END') {
            if (pair.endEvent) {
                unpaired.push(event);
            }
            else {
                pair.endEvent = event;
            }
        }
        pairs.set(key, pair);
    }
    const paired = [];
    for (const pair of pairs.values()) {
        if (pair.startEvent && pair.endEvent) {
            paired.push({
                eventType: pair.startEvent.eventType,
                startEvent: pair.startEvent,
                endEvent: pair.endEvent,
            });
        }
        else {
            if (pair.startEvent) {
                unpaired.push(pair.startEvent);
            }
            if (pair.endEvent) {
                unpaired.push(pair.endEvent);
            }
        }
    }
    return { paired, unpaired };
}
/**
 * Returns the lower-case app ID associated with an event, if any.
 */
export function getAppId(event) {
    const eventWithAppId = isMergedHistoryEvent(event) ? event.startEvent : event;
    return 'appId' in eventWithAppId ? eventWithAppId.appId?.toLowerCase() :
        undefined;
}
/**
 * A class to manage and query MergedUpdaterProcessEvent instances.
 * It builds a map of updater processes keyed by their process ID and token.
 */
export class UpdaterProcessMap {
    updaterProcesses = new Map();
    constructor(events) {
        for (const event of events) {
            if (event.eventType === 'UPDATER_PROCESS') {
                // A merged event is ill-formed if it contains events from different
                // processes.
                console.assert(UpdaterProcessMap.eventProcessKey(event.startEvent) ===
                    UpdaterProcessMap.eventProcessKey(event.endEvent));
                this.updaterProcesses.set(UpdaterProcessMap.eventProcessKey(event.startEvent), event);
            }
        }
    }
    getUpdaterProcessForEvent(event) {
        const keyEvent = isMergedHistoryEvent(event) ? event.startEvent : event;
        return this.updaterProcesses.get(UpdaterProcessMap.eventProcessKey(keyEvent));
    }
    eventDate(event) {
        const keyEvent = isMergedHistoryEvent(event) ? event.startEvent : event;
        const updaterProcess = this.getUpdaterProcessForEvent(keyEvent);
        if (updaterProcess === undefined ||
            updaterProcess.startEvent.timestamp === undefined) {
            return undefined;
        }
        // deviceUptime is in microseconds, Date.getTime() is in milliseconds.
        const startTimestampMs = updaterProcess.startEvent.timestamp.getTime();
        const startDeviceUptimeMs = updaterProcess.startEvent.deviceUptime / 1000;
        const keyEventDeviceUptimeMs = keyEvent.deviceUptime / 1000;
        return new Date(startTimestampMs - startDeviceUptimeMs + keyEventDeviceUptimeMs);
    }
    /**
     * Sorts events by date in descending order.
     * Events without dates are returned in a separate list.
     */
    sortEventsByDate(events, mergedEvents) {
        const allEvents = [
            ...events,
            ...mergedEvents,
        ];
        const eventsWithDates = [];
        const unsortedEventsWithoutDates = [];
        for (const event of allEvents) {
            const date = this.eventDate(event);
            if (date === undefined) {
                unsortedEventsWithoutDates.push(event);
            }
            else {
                eventsWithDates.push({ event, date });
            }
        }
        eventsWithDates.sort((a, b) => b.date.getTime() - a.date.getTime());
        const sortedEventsWithDates = eventsWithDates.map((item) => item.event);
        return { sortedEventsWithDates, unsortedEventsWithoutDates };
    }
    /**
     * Determines the policy set which was effective for `event` by locating its
     * most recent LOAD_POLICY event.
     */
    effectivePolicySet(event, events) {
        const process = this.getUpdaterProcessForEvent(event);
        if (process === undefined) {
            return undefined;
        }
        const eventTime = isMergedHistoryEvent(event) ?
            event.startEvent.deviceUptime :
            event.deviceUptime;
        const loadPolicyEvents = events.filter((e) => isMergedHistoryEvent(e) &&
            e.eventType === 'LOAD_POLICY' &&
            e.endEvent.policySet !== undefined &&
            e.startEvent.pid === process.startEvent.pid &&
            e.startEvent.processToken === process.startEvent.processToken &&
            e.startEvent.deviceUptime <= eventTime);
        if (loadPolicyEvents.length === 0) {
            return undefined;
        }
        return loadPolicyEvents
            .reduce((prev, current) => prev.endEvent.deviceUptime > current.endEvent.deviceUptime ?
            prev :
            current)
            .endEvent.policySet;
    }
    /**
     * Generates a key for an event unique to its process, based on its PID and
     * processToken.
     */
    static eventProcessKey(event) {
        return JSON.stringify([event.pid, event.processToken]);
    }
}
/**
 * Determines the outcome of a an update event, which may be a
 * CommonUpdateOutcome.
 */
export function getUpdateOutcome(event) {
    const endEvent = isMergedHistoryEvent(event) ? event.endEvent : event;
    return endEvent.updateStates.at(-1)?.state ?? endEvent.result;
}
