mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
c8ce7cdb37
The five cross-session index tables (SessionsByTerminal, SessionsByStage, DeadEndsByReason, ClassifierVerdictsByIdLevel, SignalsByKind) were write-only since M5.d: no endpoint, MCP tool or web page ever read them, while the dual-write cost a serial queue send per signal/transition on the ingest path, a 5-13 min full scan of Signals + DecisionTransitions every two hours and five property-filter scans per session deletion (audit 2026-09-05 F05). Removed: the tables from TableNames.All (schema hash changes, next cold start runs a full pass without them), the telemetry-index-reconcile queue name, producer, envelope, handler, queue function and 2h timer, the cross-partition Timestamp scans on the signal/transition repositories, the EnableIndexDualWrite flag (model, config mapping, admin UI, generated wire types), cascade steps 11-15 (later steps renumbered 11-18), the deletion wrappers and the offboarding wipe entries. DiscriminatorPkProp stays as a step class so manifests written before this change still resume. Order of operations: deploy first, then delete the queue pair and the five tables in the portal (B-88h). Deleting the tables before the deploy 500s every delete preview/enqueue and poisons offboarding. Submodules: .claude/commands (import_session.py), internal (D-196, docs, plan). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
66 lines
2.8 KiB
C#
66 lines
2.8 KiB
C#
using System.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
using AutopilotMonitor.Functions.Services;
|
|
using Microsoft.Azure.Functions.Worker;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AutopilotMonitor.Functions.Functions.Maintenance
|
|
{
|
|
/// <summary>
|
|
/// Hourly interleave for the stalled-session sweep
|
|
/// (<see cref="MaintenanceService.MarkStalledSessionsAsTimedOutAsync"/>). The 2h
|
|
/// <c>Maintenance</c> timer keeps running the sweep as its first step; this function adds an
|
|
/// offset tick at minute 30, so between the two the sweep effectively runs every hour and the
|
|
/// sweep-induced classification latency drops from ≤2h to ≤1h (a 5h-silent session is
|
|
/// terminalized after ~6h worst case instead of ~7h). Thresholds (2h silence → Stalled,
|
|
/// SessionTimeoutHours → terminal) are unchanged — only the sampling rate rises.
|
|
/// <para>
|
|
/// No lease/serialization on purpose (unlike <see cref="SessionDeletionMaintenanceFunction"/>):
|
|
/// the sweep is idempotent — terminal states are never overwritten, the silence guard protects
|
|
/// live sessions, and a doubled pass is a no-op — so overlapping with the 2h run is harmless.
|
|
/// Extraction pattern per the cadence-split precedent documented in
|
|
/// <c>MaintenanceService.RunAllAsync</c> (SessionDeletionMaintenanceFunction).
|
|
/// </para>
|
|
/// </summary>
|
|
public class SessionSweepFunction
|
|
{
|
|
// Minute 30 — deliberately offset from the 2h Maintenance timer (minute 0) so the two
|
|
// never fire together. const per repo convention (see MaintenanceFunction.cs).
|
|
private const string Cron = "0 30 * * * *";
|
|
|
|
private readonly MaintenanceService _maintenanceService;
|
|
private readonly OpsEventService _opsEventService;
|
|
private readonly ILogger<SessionSweepFunction> _logger;
|
|
|
|
public SessionSweepFunction(
|
|
MaintenanceService maintenanceService,
|
|
OpsEventService opsEventService,
|
|
ILogger<SessionSweepFunction> logger)
|
|
{
|
|
_maintenanceService = maintenanceService;
|
|
_opsEventService = opsEventService;
|
|
_logger = logger;
|
|
}
|
|
|
|
[Function("SessionSweep")]
|
|
public async Task Run([TimerTrigger(Cron)] object timer)
|
|
{
|
|
_logger.LogInformation("SessionSweep timer trigger fired");
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
var result = await _maintenanceService.MarkStalledSessionsAsTimedOutAsync();
|
|
sw.Stop();
|
|
|
|
if (result.Error is null)
|
|
{
|
|
await _opsEventService.RecordSessionSweepCompletedAsync(
|
|
result.StalledMarked, result.TimedOut, (int)sw.ElapsedMilliseconds);
|
|
}
|
|
else
|
|
{
|
|
await _opsEventService.RecordSessionSweepFailedAsync(result.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|