diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DeadlineNames.cs b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DeadlineNames.cs
index 24068436..f953615f 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Engine/DeadlineNames.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Engine/DeadlineNames.cs
@@ -53,5 +53,30 @@ namespace AutopilotMonitor.DecisionCore.Engine
/// On ImeUserSessionCompleted: matched pattern id.
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.
+
+ /// On InformationalEvent: mandatory. Becomes EnrollmentEvent.EventType.
+ public const string EventType = "eventType";
+
+ /// On InformationalEvent: mandatory. Becomes EnrollmentEvent.Source.
+ public const string Source = "source";
+
+ /// On InformationalEvent: optional. Becomes EnrollmentEvent.Message.
+ public const string Message = "message";
+
+ /// On InformationalEvent: optional. Enum-name string (e.g. "Info", "Warning", "Error"); unknown / missing → Info.
+ public const string Severity = "severity";
+
+ /// On InformationalEvent: optional. "true" / "false"; missing → false.
+ public const string ImmediateUpload = "immediateUpload";
+
+ /// On InformationalEvent: optional. Pre-serialized JSON object whose top-level keys are copied into the emitted event's Data dictionary.
+ public const string DataJson = "dataJson";
}
}
diff --git a/src/Shared/AutopilotMonitor.DecisionCore/Signals/DecisionSignalKind.cs b/src/Shared/AutopilotMonitor.DecisionCore/Signals/DecisionSignalKind.cs
index b9f28796..d7a8d208 100644
--- a/src/Shared/AutopilotMonitor.DecisionCore/Signals/DecisionSignalKind.cs
+++ b/src/Shared/AutopilotMonitor.DecisionCore/Signals/DecisionSignalKind.cs
@@ -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,
}
}
diff --git a/tests/fixtures/signal-kinds/informational-event-v1.json b/tests/fixtures/signal-kinds/informational-event-v1.json
new file mode 100644
index 00000000..a7a5159a
--- /dev/null
+++ b/tests/fixtures/signal-kinds/informational-event-v1.json
@@ -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}"}}