feat(web,mcp): the IME overwrite counters surface — session page row "Overwritten log blocks recovered"; get_session_summary coverage carries overwriteRewinds/overwriteBytesReprocessed and a recovered-not-missing note, never a gap (D-246, P2)

The agent (v2.0.1458) counts the log blocks it re-read after a concurrent IME process had overwritten bytes behind its bookmark. The IME Tracker rows on the session page show the count and bytes as their own row, apart from Skipped work; the MCP coverage block adds both counters to sessionTotals and, when rewinds > 0, an imeTracker.note saying the script results and exit codes in those blocks are complete — no coverage.gaps line, no degraded flag, so an assistant does not read recovered data as a blind spot. Sessions from older agents carry neither key and show neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-09-11 00:14:55 +02:00
parent bb6df93b51
commit 5ecb2a998b
3 changed files with 60 additions and 2 deletions
@@ -132,7 +132,7 @@ describe('buildSessionCoverage — IME log tracker', () => {
ev('ime_tracker_degraded', 50, { file: 'AppWorkload.log', lineBudgetBreaks: 1, regexTimeouts: 0, oversizedLines: 0 }),
ev('ime_pattern_hits', 900, { linesRead: 12000, lineBudgetBreaks: 7, regexTimeouts: 2, oversizedLines: 1 }, { severity: 'Debug' }),
]);
expect(c.imeTracker.sessionTotals).toEqual({ linesRead: 12000, lineBudgetBreaks: 7, regexTimeouts: 2, oversizedLines: 1 });
expect(c.imeTracker.sessionTotals).toEqual({ linesRead: 12000, lineBudgetBreaks: 7, regexTimeouts: 2, oversizedLines: 1, overwriteRewinds: null, overwriteBytesReprocessed: null });
expect(c.gaps[0]).toContain('skipped the remaining patterns on 7 line(s)');
expect(c.gaps[0]).toContain('gave up on 2 line(s) after a regex timeout');
expect(c.gaps[0]).toContain('dropped 1 oversized line(s)');
@@ -143,9 +143,30 @@ describe('buildSessionCoverage — IME log tracker', () => {
ev('agent_started', 0),
ev('ime_pattern_hits', 900, { linesRead: 800, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0 }),
]);
expect(c.imeTracker).toEqual({ degraded: false, sessionTotals: { linesRead: 800, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0 } });
expect(c.imeTracker).toEqual({ degraded: false, sessionTotals: { linesRead: 800, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0, overwriteRewinds: null, overwriteBytesReprocessed: null } });
expect(c.gaps).toEqual([]);
});
it('reports re-read overwritten log blocks as recovered data — a note, never a gap or degradation', () => {
// Agents ≥ 2.0.1458: IME processes overwrite each other's bytes in AgentExecutor.log and
// IntuneManagementExtension.log; the tracker re-reads the changed blocks and counts them.
const c = buildSessionCoverage(SESSION, [
ev('agent_started', 0),
ev('ime_pattern_hits', 900, { linesRead: 8957, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0, overwriteRewinds: 6, overwriteBytesReprocessed: 5120 }, { severity: 'Debug' }),
]);
expect(c.imeTracker.degraded).toBe(false);
expect(c.imeTracker.sessionTotals).toEqual({ linesRead: 8957, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0, overwriteRewinds: 6, overwriteBytesReprocessed: 5120 });
expect(c.imeTracker.note).toBe('6 log block(s) a concurrent IME process had overwritten were re-read (5 KB) — script results and exit codes in them are complete, not missing.');
expect(c.gaps).toEqual([]);
});
it('adds no note when nothing was overwritten or the agent predates the counter', () => {
const c = buildSessionCoverage(SESSION, [
ev('agent_started', 0),
ev('ime_pattern_hits', 900, { linesRead: 800, lineBudgetBreaks: 0, regexTimeouts: 0, oversizedLines: 0, overwriteRewinds: 0, overwriteBytesReprocessed: 0 }),
]);
expect(c.imeTracker.note).toBeUndefined();
});
});
describe('buildSessionCoverage — collectors, upload, disk', () => {
@@ -46,6 +46,7 @@ export const COVERAGE_EVENT_FIELDS =
'data.bootToAgentStartSeconds,data.agentUptimeSeconds,data.outcome,data.note,' +
'data.earliestRejectedSourceTimestamp,' +
'data.file,data.firstSkippedPattern,data.lineBudgetBreaks,data.regexTimeouts,data.oversizedLines,data.unanchoredPatterns,data.linesRead,' +
'data.overwriteRewinds,data.overwriteBytesReprocessed,' +
'data.collector,data.reason,data.errorType,' +
'data.pendingItemCount,data.pendingBytes,data.kind,data.itemCount,data.disk_free_gb,' +
'data.truncated,data.includedFiles,data.includedBytes,data.skippedFiles,data.skippedByReason,data.problemsByKind,' +
@@ -87,7 +88,15 @@ export interface ImeTrackerCoverage {
lineBudgetBreaks: number | null;
regexTimeouts: number | null;
oversizedLines: number | null;
/**
* Times the tracker re-read a log block a concurrent IME process had written over bytes
* it had already read (agents ≥ 2.0.1458). Recovered data — never a gap and not degradation.
*/
overwriteRewinds: number | null;
overwriteBytesReprocessed: number | null;
};
/** Present when overwriteRewinds > 0: says in words that those blocks were recovered, not lost. */
note?: string;
}
export interface CollectorDegradation {
@@ -276,8 +285,20 @@ export function buildSessionCoverage(
lineBudgetBreaks: num(hits.data?.lineBudgetBreaks),
regexTimeouts: num(hits.data?.regexTimeouts),
oversizedLines: num(hits.data?.oversizedLines),
overwriteRewinds: num(hits.data?.overwriteRewinds),
overwriteBytesReprocessed: num(hits.data?.overwriteBytesReprocessed),
}
: undefined;
// IME processes append to AgentExecutor.log and IntuneManagementExtension.log with independent
// stream positions and overwrite each other; the tracker re-reads what changed. That is
// recovered data — it must never read as a gap or as a degraded tracker.
const rewinds = sessionTotals?.overwriteRewinds ?? 0;
const overwriteNote =
rewinds > 0
? `${rewinds} log block(s) a concurrent IME process had overwritten were re-read` +
`${sessionTotals?.overwriteBytesReprocessed ? ` (${Math.round(sessionTotals.overwriteBytesReprocessed / 1024)} KB)` : ''}` +
' — script results and exit codes in them are complete, not missing.'
: undefined;
let imeTracker: ImeTrackerCoverage;
if (degradedEv) {
imeTracker = {
@@ -309,6 +330,7 @@ export function buildSessionCoverage(
} else {
imeTracker = { degraded: false, ...(sessionTotals ? { sessionTotals } : {}) };
}
if (overwriteNote) imeTracker.note = overwriteNote;
// ── Collectors ──────────────────────────────────────────────────────────────
const collectors: CollectorDegradation[] = [];
@@ -220,6 +220,10 @@ export default function DeviceDetailsCard({ events, latestAgentVersion, session
];
const imeSkipParts = imeSkipCounters.filter(([, v]) => Number(v) > 0).map(([k, v]) => `${v} ${k}`);
const imeSkips = imeSkipParts.length;
// Log blocks a concurrent Intune process wrote over after the agent had read them, re-read by
// the agent (agents ≥ 2.0.1458). Recovered data — shown apart from "Skipped work" on purpose.
const imeOverwriteRewinds = patternHits?.overwriteRewinds ?? imeTracker?.ime_overwrite_rewinds;
const imeOverwriteBytes = Number(patternHits?.overwriteBytesReprocessed ?? imeTracker?.ime_overwrite_bytes_reprocessed ?? 0);
const apiLatencyMs = session?.avgApiLatencyMs ?? 0;
const hasConnectivity = apiLatencyMs > 0 || bandwidthEstimate || metricsSnapshot;
@@ -676,6 +680,17 @@ export default function DeviceDetailsCard({ events, latestAgentVersion, session
? "Matching was cut short on some log lines — pattern coverage may be incomplete for this session. Nothing to fix on your side; use Report Session if the stats look wrong."
: undefined}
/>
{imeOverwriteRewinds !== undefined && (
<DetailRow
label="Overwritten log blocks recovered"
value={Number(imeOverwriteRewinds) > 0
? `${Number(imeOverwriteRewinds)}${imeOverwriteBytes > 0 ? ` (${formatBytesCompact(imeOverwriteBytes)} re-read)` : ""}`
: "none"}
title={Number(imeOverwriteRewinds) > 0
? "Intune processes wrote over log lines the agent had already read; the agent re-read them, so script results and exit codes in those blocks are complete. Nothing to fix on your side."
: undefined}
/>
)}
{(patternHits?.unanchoredPatterns ?? imeTracker?.ime_unanchored_patterns) !== undefined && Number(patternHits?.unanchoredPatterns ?? imeTracker?.ime_unanchored_patterns) > 0 && (
<DetailRow label="Unanchored patterns" value={String(patternHits?.unanchoredPatterns ?? imeTracker?.ime_unanchored_patterns)} />
)}