Portal, MCP and backend apply script_output_reconciliation: a corrected platform run shows its own output and "exit n/a" (D-316)

Web: the script panel applies the correction to the run its runId names: the saved output replaces the emitted one, a foreign end block's exit code shows as n/a with an explanation, the state follows the corrected data instead of the emitted event type, and the output carries a short "from IME's saved result" note. The timeline marks the original row as corrected later, and the correction event falls under the script-output setting like the other script events. The session page passes the event to the panel; the gather-rule event list knows the type.

MCP: get_session_summary no longer counts a script_failed as an error, ranks it as one or attaches the exit code's catalog text when a foreign correction with IME result Success names its run.

Backend: the correction never heals a stalled session and never counts as a script run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-10-04 15:15:07 +02:00
parent 672570df04
commit f392819b97
11 changed files with 388 additions and 14 deletions
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared;
using AutopilotMonitor.Shared.Models;
using Xunit;
namespace AutopilotMonitor.Functions.Tests;
/// <summary>
/// D-316: the agent corrects an already reported platform run from IME's saved result with a
/// script_output_reconciliation. It is observation of an earlier run: it never heals a Stalled session and never
/// counts as another script run.
/// </summary>
public class ScriptOutputReconciliationClassificationTests
{
private static readonly DateTime At = new(2026, 10, 3, 20, 59, 8, DateTimeKind.Utc);
[Fact]
public void A_correction_alone_is_no_real_activity_and_no_script_run()
{
var classification = EventIngestProcessor.ClassifyEvents(new List<EnrollmentEvent>
{
Event(Constants.EventTypes.ScriptOutputReconciliation, new Dictionary<string, object> { ["scriptType"] = "platform", ["runId"] = "run-1", ["outcome"] = "foreign" }),
});
Assert.False(classification.HasNonPeriodicRealEvent);
Assert.Equal(0, classification.PlatformScriptCount);
}
[Fact]
public void The_corrected_run_counts_once_as_a_platform_script()
{
var classification = EventIngestProcessor.ClassifyEvents(new List<EnrollmentEvent>
{
Event("script_failed", new Dictionary<string, object> { ["scriptType"] = "platform", ["runId"] = "run-1", ["exitCode"] = "1" }),
Event(Constants.EventTypes.ScriptOutputReconciliation, new Dictionary<string, object> { ["scriptType"] = "platform", ["runId"] = "run-1", ["outcome"] = "foreign" }),
});
Assert.True(classification.HasNonPeriodicRealEvent);
Assert.Equal(1, classification.PlatformScriptCount);
}
private static EnrollmentEvent Event(string eventType, Dictionary<string, object> data) => new()
{
EventId = Guid.NewGuid().ToString(),
Timestamp = At,
EventType = eventType,
Source = "RegistryScriptResult",
Data = data,
};
}
@@ -480,6 +480,9 @@ namespace AutopilotMonitor.Functions.Services
// observation too — a device that slept through its stall must stay Stalled.
"system_clock_changed" => true,
"system_sleep_episode" => true,
// A platform run corrected after the fact from IME's saved result (D-316): registry
// observation of an earlier run, not progress — a stalled session stays Stalled.
Constants.EventTypes.ScriptOutputReconciliation => true,
_ => false
};
@@ -0,0 +1,95 @@
/**
* D-316: a platform run whose script_failed came from another script's end block is corrected by a later
* script_output_reconciliation (outcome "foreign", IME result Success). get_session_summary must not count the
* stored script_failed as an error, must not rank it as one and must not attach the foreign exit code's catalog
* text; the correction itself stays a Warning key event.
*
* Pure unit tests: fetch is stubbed and the registered tool handler is invoked directly.
*/
import { describe, it, expect, vi, afterEach } from 'vitest';
import { McpServer } from '@modelcontextprotocol/server';
import { registerTools } from '../tools.js';
import { runWithCaller } from '../client.js';
import { foreignSuccessRunIds, isCorrectedForeignFailure } from '../tools/shared.js';
type ToolHandler = (args: Record<string, unknown>, extra: unknown) => Promise<{ content?: Array<{ type: string; text?: string }> }>;
const SESSION = 'e259c121-1234-4abc-9def-0123456789ab';
const GA = { token: 'ga', isGlobalAdmin: true };
const extra = { signal: new AbortController().signal };
function summaryHandler(): ToolHandler {
const server = new McpServer({ name: 'test', version: '0.0.0' });
registerTools(server, undefined, undefined, undefined, true, true, false);
const registry = (server as unknown as { _registeredTools: Record<string, { handler: ToolHandler }> })._registeredTools;
return registry['get_session_summary'].handler;
}
function stubBackend(events: unknown[]): void {
const body = {
success: true,
session: { sessionId: SESSION, tenantId: 't', status: 'Succeeded', startedAt: '2026-10-03T20:49:00Z' },
events,
results: [],
annotations: [],
count: events.length,
};
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, json: async () => body, text: async () => JSON.stringify(body) }) as unknown as Response));
}
afterEach(() => vi.unstubAllGlobals());
const foreignFailure = {
timestamp: '2026-10-03T20:59:04Z',
eventType: 'script_failed',
severity: 'Error',
source: 'ImeLogTracker',
message: 'Platform script d94468af: Success (exit: 1)',
data: { scriptType: 'platform', result: 'Success', exitCode: '1', runId: 'run-1' },
};
const correction = (data: Record<string, unknown>) => ({
timestamp: '2026-10-03T20:59:08Z',
eventType: 'script_output_reconciliation',
severity: 'Warning',
source: 'RegistryScriptResult',
message: "Platform script d94468af: output corrected from IME's saved result",
data: { scriptType: 'platform', outcome: 'foreign', result: 'Success', runId: 'run-1', ...data },
});
async function summary(events: unknown[]): Promise<Record<string, any>> {
stubBackend(events);
const result = await runWithCaller(GA, () => summaryHandler()({ sessionId: SESSION }, extra));
return JSON.parse((result.content ?? []).map((c) => c.text ?? '').join(''));
}
describe('get_session_summary with a corrected platform run', () => {
it('does not count the foreign script_failed as an error and drops its exit-code text', async () => {
const body = await summary([foreignFailure, correction({})]);
expect(body.stats.errorCount).toBe(0);
expect(body.stats.warningCount).toBe(1);
const failed = body.keyEvents.find((e: { eventType: string }) => e.eventType === 'script_failed');
expect(failed.correctedByImeSavedResult).toBe(true);
expect(failed.errorCode).toBeUndefined();
expect(body.keyEvents.some((e: { eventType: string }) => e.eventType === 'script_output_reconciliation')).toBe(true);
});
it('keeps counting a failure IME itself reported, and an uncorrected one', async () => {
expect((await summary([foreignFailure, correction({ result: 'Failed' })])).stats.errorCount).toBe(1);
expect((await summary([foreignFailure, correction({ outcome: 'repaired' })])).stats.errorCount).toBe(1);
expect((await summary([foreignFailure, correction({ runId: 'another-run' })])).stats.errorCount).toBe(1);
expect((await summary([foreignFailure])).stats.errorCount).toBe(1);
});
});
describe('foreignSuccessRunIds / isCorrectedForeignFailure', () => {
it('names only foreign corrections whose IME result is Success, and only script_failed rows', () => {
const runs = foreignSuccessRunIds([correction({}), correction({ runId: 'r2', result: 'Failed' }), correction({ runId: 'r3', outcome: 'repaired' })]);
expect([...runs]).toEqual(['run-1']);
expect(isCorrectedForeignFailure('script_failed', { runId: 'run-1' }, runs)).toBe(true);
expect(isCorrectedForeignFailure('script_completed', { runId: 'run-1' }, runs)).toBe(false);
expect(isCorrectedForeignFailure('script_failed', { runId: 'r2' }, runs)).toBe(false);
expect(isCorrectedForeignFailure('script_failed', undefined, runs)).toBe(false);
});
});
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/server';
import { z } from 'zod';
import { ApiError, apiFetch, buildQuery, jsonBody, DEFAULT_FIRST_PAGE_SIZE, effectivePageSize, enforceDelegatedTenant, enforceDelegatedTenantForPage, followNextLink, pageSizeForCall, pickGlobalOrTenantPath, scanUntilMatch, scanWithTimeoutFallback } from '../client.js';
import { withToolTelemetry } from '../telemetry.js';
import { READ_ONLY, MAX_RESULT_SIZE_CHARS, LEAN_EVENT_FIELDS, LEAN_EVENT_OMISSION, leanFieldSelection, SUMMARY_EVENT_FIELDS, toolResultText, SessionIdSchema, isBenignHealthDetectionReport, tenantIdDescription, pageSizeDescription, CONTINUATION_DESCRIPTION, daysDescription } from './shared.js';
import { READ_ONLY, MAX_RESULT_SIZE_CHARS, LEAN_EVENT_FIELDS, LEAN_EVENT_OMISSION, leanFieldSelection, SUMMARY_EVENT_FIELDS, toolResultText, SessionIdSchema, isBenignHealthDetectionReport, foreignSuccessRunIds, isCorrectedForeignFailure, tenantIdDescription, pageSizeDescription, CONTINUATION_DESCRIPTION, daysDescription } from './shared.js';
import { toolError } from './error-handler.js';
import { lookupErrorCode } from '../error-code-catalog.js';
import { withEventTypeNote, assertKnownDevicePropertyKeys } from '../resource-catalog.js';
@@ -534,6 +534,8 @@ export function registerSessionTools(server: McpServer, ga: boolean, delegated:
// Observation coverage: what the agent could and could not see, folded from the
// health/lifecycle events that the triage timeline below ranks low or drops.
const coverage = buildSessionCoverage(s, allEvents);
// Platform runs IME's saved result proved to carry another script's exit code (D-316).
const foreignRuns = foreignSuccessRunIds(allEvents);
let errorCount = 0;
let warningCount = 0;
@@ -545,7 +547,8 @@ export function registerSessionTools(server: McpServer, ga: boolean, delegated:
const sev = String(e.severity ?? '');
// A compliant health-script detection mis-stamped script_failed/Error is benign —
// exclude it so a green session's errorCount isn't inflated by routine compliance reports.
const benign = isBenignHealthDetectionReport(String(e.eventType ?? ''), e.data);
const benign = isBenignHealthDetectionReport(String(e.eventType ?? ''), e.data)
|| isCorrectedForeignFailure(String(e.eventType ?? ''), e.data, foreignRuns);
if (!benign && (sev === 'Error' || sev === 'Critical')) errorCount++;
if (sev === 'Warning') warningCount++;
const et = String(e.eventType ?? '');
@@ -571,6 +574,8 @@ export function registerSessionTools(server: McpServer, ga: boolean, delegated:
const relevanceScore = (e: Partial<EnrollmentEvent>): number => {
// Benign compliant detection mis-stamped Error → rank as info-level, not top.
if (isBenignHealthDetectionReport(String(e.eventType ?? ''), e.data)) return 10;
// A failure that was another script's exit code is not a failure of this one.
if (isCorrectedForeignFailure(String(e.eventType ?? ''), e.data, foreignRuns)) return 10;
const sev = SEVERITY_RANK[String(e.severity ?? '')] ?? -1;
if (sev >= 3) return 100; // Error/Critical
if (PHASE_EVENT_TYPES.has(String(e.eventType ?? ''))) return 60;
@@ -597,7 +602,10 @@ export function registerSessionTools(server: McpServer, ga: boolean, delegated:
phase: phaseName(e.phase, s.enrollmentType),
message: e.message,
source: e.source,
...keyEventErrorCode(e.data, e.source),
// The exit code on a corrected run was another script's: no catalog text for it.
...(isCorrectedForeignFailure(String(e.eventType ?? ''), e.data, foreignRuns)
? { correctedByImeSavedResult: true }
: keyEventErrorCode(e.data, e.source)),
}));
let analysis = null;
@@ -75,6 +75,8 @@ export function leanFieldSelection(
export const SUMMARY_EVENT_FIELDS =
'eventType,severity,source,phase,timestamp,message,sequence,' +
'data.scriptType,data.script_type,data.scriptPart,data.script_part,data.result,' +
// Platform-run corrections from IME's saved result (foreignSuccessRunIds below).
'data.runId,data.outcome,' +
'data.rejectedSourceTimestamp,data.rejected_source_timestamp,' +
'data.errorCode,data.exitCode,data.hresult,data.hresultFromWin32,data.hresultSymbol,' +
'data.errorCodeInfo,data.exitCodeInfo,data.hresultInfo,data.hresultFromWin32Info,' +
@@ -125,6 +127,38 @@ export function isBenignHealthDetectionReport(
return true;
}
/**
* Run ids of platform runs whose `script_failed` came from another script's end block while IME
* itself reported Success: `script_output_reconciliation` with outcome "foreign" and result Success
* (D-316 — the agent had attributed a foreign executor's exit code and output; IME's saved result
* proved it). The stored `script_failed` stays as written; read sides treat it like the benign
* detection reports above: no error count, info-level rank, no exit-code catalog text.
*/
export function foreignSuccessRunIds(
events: ReadonlyArray<{ eventType?: string | null; data?: Record<string, unknown> }>,
): Set<string> {
const runs = new Set<string>();
for (const e of events) {
if (e.eventType !== 'script_output_reconciliation' || !e.data) continue;
if (String(e.data.outcome ?? '') !== 'foreign') continue;
if (String(e.data.result ?? '').toLowerCase() !== 'success') continue;
const runId = e.data.runId;
if (typeof runId === 'string' && runId.length > 0) runs.add(runId);
}
return runs;
}
/** True for a `script_failed` whose run `foreignSuccessRunIds` names. */
export function isCorrectedForeignFailure(
eventType: string | undefined,
data: Record<string, unknown> | undefined,
foreignRuns: ReadonlySet<string>,
): boolean {
if (eventType !== 'script_failed' || !data || foreignRuns.size === 0) return false;
const runId = data.runId;
return typeof runId === 'string' && foreignRuns.has(runId);
}
/**
* Builds the role-aware description for a tenant-boundable tool's `tenantId` argument. MCP clients weigh
* the per-arg schema heavily, so a delegated (MSP) caller — for whom omitting tenantId is REJECTED, not
@@ -152,6 +152,8 @@ export const KNOWN_EVENT_TYPES: EventTypeEntry[] = [
description: "A platform script completed, or one phase (detection / remediation / post-detection) of a health script completed. Health scripts emit up to three of these per policy run." },
{ value: "script_failed", label: "script_failed", category: "script",
description: "A platform script crashed (exit code != 0), or the remediation phase of a health script crashed. Health-script detection / post-detection phases use exit code as a compliance verdict (non-zero = non-compliant) and emit script_completed even on non-zero exit; only a true script crash in those phases would surface here." },
{ value: "script_output_reconciliation", label: "script_output_reconciliation", category: "script",
description: "A platform script's reported output disagreed with the result IME saved after reporting the script batch. Outcome \"foreign\" (Warning): the run had been given another script's output and exit code; the saved output replaces it and the exit code is unknown. Outcome \"repaired\" (Info): the output had arrived incomplete. Carries runId (the corrected run), policyId, outcome, reason, result, stdout and stderr." },
{ value: "script_timeout_suspected", label: "script_timeout_suspected", category: "script",
description: "A platform script ran to the IME script-execution timeout (~30 min; threshold 25 min) and was marked Failed while the enrollment was in progress. IME runs platform scripts serially, so a hung script starves app installs and the Autopilot-Monitor bootstrap — the prime suspect behind a late agent start / pre-failed ESP. Advisory (one per policyId); carries durationSeconds, exitCode and espPhase." },
{ value: "script_recurrence_summary", label: "script_recurrence_summary", category: "script",
@@ -1,6 +1,6 @@
"use client";
import { useState, useMemo, useDeferredValue } from "react";
import { createContext, useContext, useState, useMemo, useDeferredValue } from "react";
import { EnrollmentEvent, Session } from "@/types";
import { normalizeEventDataForDisplay, shortenBuildHashInMessage } from "../utils/eventHelpers";
import { buildEventSearchMatcher, formatEventSearchTerm, parseEventSearchQuery } from "../utils/eventSearchQuery";
@@ -8,6 +8,11 @@ import { getEnrichedOrLookup, formatErrorCode, errorCodeTooltip, type ErrorCodeI
import { readTimeProvenance, classifyTimeJump, readClockChangeDeltaMs } from "@/lib/timeProvenance";
import { isRebootOrRetryClass } from "@/lib/installProgress";
import { formatDuration, formatUtcOffset } from "@/lib/formatting";
import { SCRIPT_OUTPUT_RECONCILIATION } from "@/lib/scriptExecutions";
// runId → outcome of every platform run that IME's saved result corrected later (D-316): the
// run's own row says so, so its emitted output is never read as the script's.
const CorrectedRunsContext = createContext<ReadonlyMap<string, "foreign" | "repaired">>(new Map());
const SEARCH_SYNTAX_HINT =
"Searches event type, message, source and the details JSON (e.g. gather output). " +
@@ -104,10 +109,20 @@ export default function EventTimeline({
[events],
);
const correctedRuns = useMemo(() => {
const runs = new Map<string, "foreign" | "repaired">();
for (const e of events) {
if (e.eventType !== SCRIPT_OUTPUT_RECONCILIATION) continue;
const runId = e.data?.runId;
if (typeof runId === "string" && runId.length > 0) runs.set(runId, e.data?.outcome === "foreign" ? "foreign" : "repaired");
}
return runs;
}, [events]);
const filterPhaseEvents = (phaseEvents: EnrollmentEvent[]) =>
matchesSearch ? phaseEvents.filter(matchesSearch) : phaseEvents;
return (
const timeline = (
<div className="space-y-6">
{/* Search + Severity filters + Expand/Collapse — shared controls above the timeline(s) */}
<div className="flex flex-col sm:flex-row sm:items-center gap-2">
@@ -356,6 +371,8 @@ export default function EventTimeline({
)}
</div>
);
return <CorrectedRunsContext.Provider value={correctedRuns}>{timeline}</CorrectedRunsContext.Provider>;
}
function PhaseSection({
@@ -515,7 +532,13 @@ function EventRow({ event, showScriptOutput, prevEvent, clockDeltas }: { event:
// timeline still applies the same script-event styling/iconography.
const isScriptEvent = event.eventType === "script_started"
|| event.eventType === "script_completed"
|| event.eventType === "script_failed";
|| event.eventType === "script_failed"
|| event.eventType === SCRIPT_OUTPUT_RECONCILIATION;
const correctedRuns = useContext(CorrectedRunsContext);
const eventRunId = event.data?.runId;
const laterCorrection = event.eventType !== SCRIPT_OUTPUT_RECONCILIATION && typeof eventRunId === "string"
? correctedRuns.get(eventRunId)
: undefined;
const detailData = useMemo(() => {
if (!rawDetailData || !isScriptEvent || showScriptOutput !== false) return rawDetailData;
const filtered = { ...rawDetailData };
@@ -611,6 +634,13 @@ function EventRow({ event, showScriptOutput, prevEvent, clockDeltas }: { event:
<span className="text-sm font-medium text-gray-900">{event.eventType}</span>
</div>
<p className="mt-1 text-sm text-gray-600" title={event.message || undefined}>{shortenBuildHashInMessage(event.message)}</p>
{laterCorrection && (
<p className="mt-0.5 text-xs text-gray-500">
{laterCorrection === "foreign"
? "Corrected later from IME's saved result: the output and exit code on this row belonged to another script."
: "Output completed later from IME's saved result."}
</p>
)}
{/* Exit code / HRESULT badge for app install events */}
{(event.eventType === "app_install_failed" || event.eventType === "app_install_completed") && (() => {
const ec = (event.data?.exitCode ?? event.data?.exit_code) as string | number | undefined;
@@ -234,6 +234,8 @@ function SessionDetailContent() {
e => e.eventType === "script_started"
|| e.eventType === "script_completed"
|| e.eventType === "script_failed"
// Corrections of a platform run from IME's saved result (D-316) — applied by the reducer.
|| e.eventType === "script_output_reconciliation"
),
[events]
);
@@ -30,6 +30,14 @@ import PhaseDivider from "@/components/PhaseDivider";
// span is hours of offset error rather than run time (a 10 s script once read "1h 00m").
const DURATION_SUPPRESSED_TITLE = "Run time not measurable: the start and end log lines were resolved on different timezone assumptions, so their span would be offset error, not run time";
// IME's saved result corrected the run (script_output_reconciliation): the log had attributed
// another script's end block to it, so its exit code is unknown and the output shown is IME's.
const EXIT_UNVERIFIED_TITLE = "Exit code unknown: the IME log had attributed another script's output and exit code to this run. The output shown is this script's own, as IME saved it.";
const OUTPUT_CORRECTED_TITLE: Record<"foreign" | "repaired", string> = {
foreign: "The IME log had attributed another script's output to this run. This is the script's own output, as IME saved it after reporting the result.",
repaired: "The IME log delivered this output incomplete. This is the full output, as IME saved it after reporting the result.",
};
interface ScriptExecutionsProps {
events: ScriptInputEvent[];
showScriptOutput?: boolean;
@@ -528,6 +536,9 @@ function ScriptItemRow({ item, showScriptOutput, latestBootstrapVersion, nested,
exit {item.exitCode}
</span>
)}
{item.exitCode == null && item.outputCorrection === "foreign" && (
<span className="font-mono text-gray-400" title={EXIT_UNVERIFIED_TITLE}>exit n/a</span>
)}
{durationLabel && (
<span
className={`font-mono ${isSlowDuration ? "text-amber-600 font-medium" : "text-gray-500"}`}
@@ -564,6 +575,9 @@ function ScriptItemRow({ item, showScriptOutput, latestBootstrapVersion, nested,
{!nested && item.runContext && <span><span className="font-medium text-gray-700">Context:</span> {item.runContext}</span>}
{!nested && item.targetType != null && <span><span className="font-medium text-gray-700">Target:</span> {item.targetType === 2 ? "Device" : "User"}</span>}
{item.exitCode != null && <span><span className="font-medium text-gray-700">Exit Code:</span> <span className="font-mono">{item.exitCode}</span></span>}
{item.exitCode == null && item.outputCorrection === "foreign" && (
<span title={EXIT_UNVERIFIED_TITLE}><span className="font-medium text-gray-700">Exit Code:</span> <span className="font-mono text-gray-400">n/a</span></span>
)}
{detailDurationLabel && <span><span className="font-medium text-gray-700">Duration:</span> <span className={`font-mono ${isSlowDuration ? "text-amber-600" : ""}`}>{detailDurationLabel}</span></span>}
{!detailDurationLabel && item.durationSuppressedReason && (
<span title={DURATION_SUPPRESSED_TITLE}><span className="font-medium text-gray-700">Duration:</span> <span className="font-mono text-gray-400">n/a</span></span>
@@ -601,7 +615,12 @@ function ScriptItemRow({ item, showScriptOutput, latestBootstrapVersion, nested,
{hasStdout && (
<div>
<div className="text-xs font-medium text-gray-500 mb-1">stdout</div>
<div className="text-xs font-medium text-gray-500 mb-1">
stdout
{item.outputCorrection && (
<span className="font-normal text-gray-400" title={OUTPUT_CORRECTED_TITLE[item.outputCorrection]}>{" · from IME's saved result"}</span>
)}
</div>
<div className="p-2 bg-gray-900 rounded text-xs text-gray-100 font-mono overflow-x-auto max-h-48 overflow-y-auto">
<pre className="whitespace-pre-wrap break-words">{item.stdout}</pre>
</div>
@@ -614,7 +633,12 @@ function ScriptItemRow({ item, showScriptOutput, latestBootstrapVersion, nested,
{hasStderr && (
<div>
<div className="text-xs font-medium text-red-500 mb-1">stderr</div>
<div className="text-xs font-medium text-red-500 mb-1">
stderr
{item.outputCorrection && (
<span className="font-normal text-gray-400" title={OUTPUT_CORRECTED_TITLE[item.outputCorrection]}>{" · from IME's saved result"}</span>
)}
</div>
<div className="p-2 bg-gray-900 rounded text-xs text-red-300 font-mono overflow-x-auto max-h-48 overflow-y-auto">
<pre className="whitespace-pre-wrap break-words">{item.stderr}</pre>
</div>
@@ -1201,3 +1201,81 @@ describe("isOnOffsetGrid", () => {
expect(isOnOffsetGrid(0)).toBe(false);
});
});
describe("reduceScriptEvents — correction from IME's saved result (D-316)", () => {
const POLICY = "d94468af-6ebe-4e94-9901-2a62d33ea6c1";
const OWN = "=== Autopilot Monitor Bootstrap Started =====\nBootstrap script version: v2.5\nInstaller finished (exit code 0).";
const FOREIGN = "VERBOSE: === Sample Suite Detection Started ===\nWARNING: === DETECTION FAILED ===";
const correction = (data: Record<string, unknown>, at = 60): ScriptInputEvent => ({
timestamp: ts(at),
eventType: "script_output_reconciliation",
data: { policyId: POLICY, scriptType: "platform", ...data },
});
it("a foreign end block is replaced: the script's own output, no exit code, Success", () => {
const items = reduceScriptEvents([
finalEvent({ eventType: "script_failed", data: { policyId: POLICY, scriptType: "platform", runId: "r1", result: "Success", exitCode: "1", stdout: FOREIGN } }),
correction({ runId: "r1", outcome: "foreign", result: "Success", stdout: OWN, stderr: "", previousExitCode: "1" }),
]);
expect(items).toHaveLength(1);
expect(items[0].state).toBe("Success");
expect(items[0].exitCode).toBeUndefined();
expect(items[0].stdout).toBe(OWN);
expect(items[0].stdout).not.toContain("Detection");
expect(items[0].outputCorrection).toBe("foreign");
expect(items[0].bootstrapVersion).toBe("2.5");
});
it("an incomplete output is completed and the exit code stands", () => {
const items = reduceScriptEvents([
finalEvent({ data: { policyId: POLICY, scriptType: "platform", runId: "r2", result: "Success", exitCode: "0" } }),
correction({ runId: "r2", outcome: "repaired", result: "Success", stdout: OWN, stderr: "" }),
]);
expect(items[0].exitCode).toBe(0);
expect(items[0].stdout).toBe(OWN);
expect(items[0].outputCorrection).toBe("repaired");
});
it("a stdout split into stderr is mended: the empty stderr clears the failure", () => {
const items = reduceScriptEvents([
finalEvent({ eventType: "script_failed", data: { policyId: POLICY, scriptType: "platform", runId: "r3", result: "Success", exitCode: "0", stdout: "Copying files", stderr: "0 errors\nDone, error =" } }),
correction({ runId: "r3", outcome: "repaired", result: "Success", stdout: "Copying files, error = 0 errors\nDone", stderr: "" }),
]);
expect(items[0].state).toBe("Success");
expect(items[0].stderr).toBeUndefined();
expect(items[0].stdout).toBe("Copying files, error = 0 errors\nDone");
});
it("a correction only applies to the run it names", () => {
const items = reduceScriptEvents([
finalEvent({ eventType: "script_failed", data: { policyId: POLICY, scriptType: "platform", runId: "r4", result: "Success", exitCode: "1", stdout: FOREIGN } }),
correction({ runId: "another-run", outcome: "foreign", result: "Success", stdout: OWN, stderr: "" }),
]);
expect(items[0].state).toBe("Failed");
expect(items[0].exitCode).toBe(1);
expect(items[0].outputCorrection).toBeUndefined();
});
it("a correction without a stdout key keeps the emitted stdout", () => {
const items = reduceScriptEvents([
finalEvent({ eventType: "script_failed", data: { policyId: POLICY, scriptType: "platform", runId: "r5", result: "Failed", exitCode: "1", stdout: "partial" } }),
correction({ runId: "r5", outcome: "repaired", result: "Failed", stderr: "Access denied" }),
]);
expect(items[0].stdout).toBe("partial");
expect(items[0].stderr).toBe("Access denied");
expect(items[0].state).toBe("Failed");
});
it("two runs of the same script never surface the corrected run's foreign output", () => {
const items = reduceScriptEvents([
finalEvent({ ts: 0, data: { policyId: POLICY, scriptType: "platform", runId: "old", result: "Success", exitCode: "0", runContext: "System", stdout: OWN } }),
finalEvent({ ts: 30, eventType: "script_failed", data: { policyId: POLICY, scriptType: "platform", runId: "r6", result: "Success", exitCode: "1", runContext: "System", stdout: FOREIGN } }),
correction({ runId: "r6", outcome: "foreign", result: "Success", stdout: OWN, stderr: "" }),
]);
expect(items).toHaveLength(1);
expect(items[0].stdout).toBe(OWN);
expect(items[0].stdout).not.toContain("Detection");
expect(items[0].state).toBe("Success");
expect(items[0].exitCode === 1).toBe(false);
});
});
@@ -36,8 +36,19 @@ interface ScriptEventData {
sourceOffsetOrigin?: string;
/** "true" when the line's own timestamp was rejected and the event was stamped from the clock. */
derivedTimestamp?: string | boolean;
/** Identity of one platform-script run; a script_output_reconciliation names the run it corrects (agents from 2026-10 on). */
runId?: string;
/** On script_output_reconciliation: "foreign" (the run had another script's end block) or "repaired". */
outcome?: string;
}
/**
* A platform run corrected after the fact from IME's saved result (D-316). Its `runId` names the
* script_completed/script_failed it corrects; a present `stdout`/`stderr` key replaces the emitted
* value (empty clears it), `outcome` "foreign" makes the emitted exit code unverified.
*/
export const SCRIPT_OUTPUT_RECONCILIATION = "script_output_reconciliation";
export interface ScriptItem {
policyId: string;
scriptType: string; // "platform" | "remediation"
@@ -80,6 +91,12 @@ export interface ScriptItem {
state: "Running" | "Success" | "Failed";
timestamp: string;
bootstrapVersion?: string | null;
/**
* Set when IME's saved result corrected the run (script_output_reconciliation): "foreign" — the
* log had given the run another script's end block, so the output shown is the script's own from
* IME and the exit code is unknown; "repaired" — the log had delivered the output incomplete.
*/
outputCorrection?: "foreign" | "repaired";
/**
* Set by applyObservationEnd, never by the fold: the script was still running when the
* session's observation ended. Terminal, neither a failure nor a success — the outcome was
@@ -198,6 +215,9 @@ export function partitionHistoricScriptEvents(events: ScriptInputEvent[]): Histo
function dataCompleteness(item: ScriptItem): number {
let score = 0;
if (item.exitCode != null) score += 4; // exit code is the most important signal
// A run corrected from IME's saved result lost a foreign exit code, not data: it must not lose
// the merge against a sibling run for that.
if (item.outputCorrection === "foreign") score += 4;
if (item.result) score += 2;
if (item.complianceResult) score += 2;
if (item.remediationStatus != null) score += 1;
@@ -224,6 +244,18 @@ export function toNumber(v: unknown): number | undefined {
return undefined;
}
/**
* The value a correction publishes for `key`, or the emitted one: a present key replaces it (an
* empty string clears it), an absent key keeps it.
*/
function correctedText(correction: ScriptEventData | undefined, key: "stdout" | "stderr", emitted: unknown): string | undefined {
if (correction && typeof correction[key] === "string") {
const value = correction[key] as string;
return value.length > 0 ? value : undefined;
}
return typeof emitted === "string" ? emitted : undefined;
}
/** Map RemediationStatus enum to the human-readable label shown in the detail panel. */
export function mapRemediationStatus(status?: number): string | null {
switch (status) {
@@ -378,7 +410,14 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
// script_started events per (policyId, scriptType) in timestamp order — the start line a
// final is paired with when its run duration is judged (see judgeDurationPair).
const startsByPolicy = new Map<string, ScriptInputEvent[]>();
// IME's saved result corrects a platform run after the fact; the correction names the run.
const correctionsByRunId = new Map<string, ScriptEventData>();
for (const evt of sorted) {
if (evt.eventType === SCRIPT_OUTPUT_RECONCILIATION) {
const c = evt.data as ScriptEventData | undefined;
if (c && typeof c.runId === "string" && c.runId.length > 0) correctionsByRunId.set(c.runId, c);
continue;
}
if (evt.eventType !== "script_started") continue;
const d = evt.data as ScriptEventData | undefined;
const startPolicyId = d?.policyId ?? d?.policy_id;
@@ -402,7 +441,10 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
const key = `${dedupeId}-${scriptType}-${scriptPart ?? ""}`;
if (policyId) policyIdsWithFinal.add(`${policyId}-${scriptType}`);
const exitCode = toNumber(d.exitCode ?? d.exit_code);
const correction = typeof d.runId === "string" ? correctionsByRunId.get(d.runId) : undefined;
const outputCorrection = correction ? (correction.outcome === "foreign" ? "foreign" : "repaired") : undefined;
// A foreign end block's exit code belongs to another script: unknown, not the emitted value.
const exitCode = outputCorrection === "foreign" ? undefined : toNumber(d.exitCode ?? d.exit_code);
// Duration normalization — the wire field durationSeconds carries two different
// semantics, named by durationBasis (see the ScriptItem field docs):
@@ -435,8 +477,9 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
const remediationStatus = toNumber(d.remediationStatus ?? d.remediation_status);
const targetType = toNumber(d.targetType ?? d.target_type);
const errorCode = toNumber(d.errorCode ?? d.error_code);
const stdout = typeof d.stdout === "string" ? d.stdout : undefined;
const stderr = typeof d.stderr === "string" ? d.stderr : undefined;
const stdout = correctedText(correction, "stdout", d.stdout);
const stderr = correctedText(correction, "stderr", d.stderr);
const result = (typeof correction?.result === "string" ? correction.result : undefined) ?? d.result;
const hasStderr = !!stderr && stderr.trim().length > 0;
// State derivation — rules in priority order (mirrors the agent's EmitScriptCompleted):
@@ -450,8 +493,10 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
// 3. Defensive: explicit script_failed eventType OR result === "Failed" → Failed.
const isHealthComplianceReport = scriptType === "remediation"
&& (scriptPart === "detection" || scriptPart === "post-detection");
const isFailureSignal = evt.eventType === "script_failed"
|| d.result === "Failed"
// A corrected run is judged on its corrected data: the agent chose script_failed from the
// emitted values the correction replaced.
const isFailureSignal = (!correction && evt.eventType === "script_failed")
|| result === "Failed"
|| (!isHealthComplianceReport && (hasStderr || (exitCode != null && exitCode !== 0)));
const candidate: ScriptItem = {
@@ -460,7 +505,7 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
scriptPart,
runContext: d.runContext ?? d.run_context,
exitCode,
result: d.result,
result,
complianceResult: d.complianceResult ?? d.compliance_result,
remediationStatus,
targetType,
@@ -476,6 +521,7 @@ export function reduceScriptEvents(events: ScriptInputEvent[]): ScriptItem[] {
timestamp: evt.timestamp,
bootstrapVersion: scriptType === "platform" ? extractBootstrapVersion(stdout) : null,
isIncomplete: false,
outputCorrection,
};
// Keep the most-complete entry, but merge the duration fields across both: the