v2 single-rail 4.1a: add InformationalEvent signal kind + payload keys

Introduce the generic pass-through signal that carries an EnrollmentEvent
shape through the decision engine. Any peripheral collector / lifecycle
source needing the Events timeline will post this instead of calling
TelemetryEventEmitter directly, so the engine is the single ordering
source for the wire. Promotion to a decision-specific kind is a later,
localized change.

- DecisionSignalKind.InformationalEvent (new).
- SignalPayloadKeys: EventType + Source (mandatory), Message, Severity,
  ImmediateUpload, DataJson (optional).
- tests/fixtures/signal-kinds/informational-event-v1.json documents the
  wire shape with a realistic NTP-probe payload.

No reducer handler yet — that is the next subtask (4.1b); this commit is
purely the contract extension.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-04-23 12:47:02 +02:00
parent 715527eda9
commit 489f124c6d
3 changed files with 49 additions and 0 deletions
@@ -53,5 +53,30 @@ namespace AutopilotMonitor.DecisionCore.Engine
/// <summary>On <c>ImeUserSessionCompleted</c>: matched pattern id.</summary>
public const string ImePatternId = "patternId";
// --- InformationalEvent payload (plan §1.3, single-rail refactor) ------------
// Mirrors the EnrollmentEvent fields the reducer must reconstruct for the
// EmitEventTimelineEntry effect. EventType / Source are mandatory; the rest are
// optional. Missing Severity defaults to Info, missing ImmediateUpload defaults
// to false. DataJson, when present, is a JSON object whose properties are merged
// into the effect parameter dictionary as individual string entries.
/// <summary>On <c>InformationalEvent</c>: mandatory. Becomes <c>EnrollmentEvent.EventType</c>.</summary>
public const string EventType = "eventType";
/// <summary>On <c>InformationalEvent</c>: mandatory. Becomes <c>EnrollmentEvent.Source</c>.</summary>
public const string Source = "source";
/// <summary>On <c>InformationalEvent</c>: optional. Becomes <c>EnrollmentEvent.Message</c>.</summary>
public const string Message = "message";
/// <summary>On <c>InformationalEvent</c>: optional. Enum-name string (e.g. "Info", "Warning", "Error"); unknown / missing → Info.</summary>
public const string Severity = "severity";
/// <summary>On <c>InformationalEvent</c>: optional. "true" / "false"; missing → false.</summary>
public const string ImmediateUpload = "immediateUpload";
/// <summary>On <c>InformationalEvent</c>: optional. Pre-serialized JSON object whose top-level keys are copied into the emitted event's Data dictionary.</summary>
public const string DataJson = "dataJson";
}
}
@@ -44,5 +44,19 @@ namespace AutopilotMonitor.DecisionCore.Signals
// --- Admin-driven preemption (Plan §2.7 admin-action audit, V2 parity PR-B3) ---
AdminPreemptionDetected,
// --- Informational pass-through (Single-Rail refactor, plan §1.3) ---
// Carries a full EnrollmentEvent payload through the reducer without mutating
// DecisionState. The HandleInformationalEventV1 reducer case emits exactly one
// EmitEventTimelineEntry effect with the payload 1:1, then yields the unchanged
// state. Any peripheral collector / lifecycle source that needs to appear on the
// Events timeline posts an InformationalEvent instead of calling TelemetryEventEmitter
// directly — the engine remains the single ordering / replay source.
//
// Promotion path: if a sender later needs its event to drive a decision, replace
// the InformationalEvent post with a specific kind (e.g. PlatformScriptCompleted)
// and add a state-mutating reducer case. Emission shape and UI contract stay the
// same because the effect parameters carry the same EnrollmentEvent fields.
InformationalEvent,
}
}
+10
View File
@@ -0,0 +1,10 @@
# InformationalEvent v1 — V2 single-rail refactor, plan §1.3.
# Reducer handler HandleInformationalEventV1 is a pass-through: it does NOT mutate
# DecisionState (LastAppliedSignalOrdinal + StepIndex are the only state deltas,
# applied by the shared reducer frame). It emits exactly one EmitEventTimelineEntry
# effect that reconstructs the full EnrollmentEvent from the payload keys defined in
# SignalPayloadKeys (eventType, source, message, severity, immediateUpload, dataJson).
# Missing optional keys use sensible defaults (severity=Info, immediateUpload=false).
# Any DataJson key is merged into the effect parameter dict so BuildDataDict in
# EventTimelineEmitter carries it into EnrollmentEvent.Data 1:1.
{"SessionSignalOrdinal":7,"SessionTraceOrdinal":7,"Kind":"InformationalEvent","KindSchemaVersion":1,"OccurredAtUtc":"2026-04-23T10:15:30.1234567Z","SourceOrigin":"StartupEnvironmentProbes.Ntp","Evidence":{"Kind":"Observed","Identifier":"ntp_check_result","Summary":"NTP offset -124.02s from time.windows.com."},"Payload":{"eventType":"ntp_time_check","source":"Network","message":"NTP time check: offset -124.02s from time.windows.com","severity":"Warning","immediateUpload":"false","dataJson":"{\"ntpServer\":\"time.windows.com\",\"offsetSeconds\":-124.02}"}}