diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/AdminConfigurationWhiteGloveSealingPatternIdsTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/AdminConfigurationWhiteGloveSealingPatternIdsTests.cs
new file mode 100644
index 00000000..da460942
--- /dev/null
+++ b/src/Backend/AutopilotMonitor.Functions.Tests/AdminConfigurationWhiteGloveSealingPatternIdsTests.cs
@@ -0,0 +1,69 @@
+using AutopilotMonitor.Shared.Models;
+using Xunit;
+
+namespace AutopilotMonitor.Functions.Tests
+{
+ ///
+ /// Tests for . Unlike the
+ /// ModernDeployment helper, this feature is off by default — the fallback for
+ /// null / empty / invalid JSON must be an empty list so M3-compatible behaviour is
+ /// preserved until an operator opts in.
+ ///
+ public sealed class AdminConfigurationWhiteGloveSealingPatternIdsTests
+ {
+ [Fact]
+ public void GetWhiteGloveSealingPatternIds_NullJson_ReturnsEmpty()
+ {
+ var cfg = new AdminConfiguration();
+
+ var ids = cfg.GetWhiteGloveSealingPatternIds();
+
+ Assert.Empty(ids);
+ }
+
+ [Fact]
+ public void GetWhiteGloveSealingPatternIds_WhitespaceJson_ReturnsEmpty()
+ {
+ var cfg = new AdminConfiguration { WhiteGloveSealingPatternIdsJson = " " };
+
+ var ids = cfg.GetWhiteGloveSealingPatternIds();
+
+ Assert.Empty(ids);
+ }
+
+ [Fact]
+ public void GetWhiteGloveSealingPatternIds_InvalidJson_ReturnsEmpty()
+ {
+ var cfg = new AdminConfiguration { WhiteGloveSealingPatternIdsJson = "not-json" };
+
+ var ids = cfg.GetWhiteGloveSealingPatternIds();
+
+ Assert.Empty(ids);
+ }
+
+ [Fact]
+ public void GetWhiteGloveSealingPatternIds_ValidJson_RoundTrips()
+ {
+ var cfg = new AdminConfiguration
+ {
+ WhiteGloveSealingPatternIdsJson = "[\"wg-seal-1\",\"wg-seal-2\",\"wg-seal-3\"]"
+ };
+
+ var ids = cfg.GetWhiteGloveSealingPatternIds();
+
+ Assert.Equal(new[] { "wg-seal-1", "wg-seal-2", "wg-seal-3" }, ids);
+ }
+
+ [Fact]
+ public void GetWhiteGloveSealingPatternIds_EmptyArray_ReturnsEmptyList()
+ {
+ // Explicit empty array means "feature opted-in but no patterns yet" — same
+ // observable behaviour as the default but distinguishable in storage.
+ var cfg = new AdminConfiguration { WhiteGloveSealingPatternIdsJson = "[]" };
+
+ var ids = cfg.GetWhiteGloveSealingPatternIds();
+
+ Assert.Empty(ids);
+ }
+ }
+}
diff --git a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
index f70f3d03..8d4baba4 100644
--- a/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
+++ b/src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableConfigRepository.cs
@@ -541,6 +541,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
{ "MaintenanceBlockDurationHours", config.MaintenanceBlockDurationHours },
{ "DiagnosticsGlobalLogPathsJson", config.DiagnosticsGlobalLogPathsJson },
{ "ModernDeploymentHarmlessEventIdsJson", config.ModernDeploymentHarmlessEventIdsJson ?? string.Empty },
+ { "WhiteGloveSealingPatternIdsJson", config.WhiteGloveSealingPatternIdsJson ?? string.Empty },
{ "NvdApiKey", config.NvdApiKey },
{ "VulnerabilityCorrelationEnabled", config.VulnerabilityCorrelationEnabled },
{ "VulnerabilityDataLastSyncUtc", config.VulnerabilityDataLastSyncUtc },
@@ -593,6 +594,7 @@ namespace AutopilotMonitor.Functions.DataAccess.TableStorage
MaintenanceBlockDurationHours = entity.GetInt32("MaintenanceBlockDurationHours") ?? 12,
DiagnosticsGlobalLogPathsJson = entity.GetString("DiagnosticsGlobalLogPathsJson"),
ModernDeploymentHarmlessEventIdsJson = entity.GetString("ModernDeploymentHarmlessEventIdsJson"),
+ WhiteGloveSealingPatternIdsJson = entity.GetString("WhiteGloveSealingPatternIdsJson"),
NvdApiKey = entity.GetString("NvdApiKey"),
VulnerabilityCorrelationEnabled = entity.GetBoolean("VulnerabilityCorrelationEnabled") ?? true,
VulnerabilityDataLastSyncUtc = entity.GetString("VulnerabilityDataLastSyncUtc"),
diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
index 36540dd5..8a027f6a 100644
--- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs
@@ -141,7 +141,7 @@ namespace AutopilotMonitor.Functions.Functions.Config
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new AgentConfigResponse
{
- ConfigVersion = 25, // IntegrityBypassAnalyzer flag
+ ConfigVersion = 26, // WhiteGloveSealingPatternIds wired from AdminConfiguration
UploadIntervalSeconds = 10,
SelfDestructOnComplete = tenantConfig.SelfDestructOnComplete ?? true,
KeepLogFile = tenantConfig.KeepLogFile ?? false,
@@ -176,7 +176,8 @@ namespace AutopilotMonitor.Functions.Functions.Config
SendTraceEvents = tenantConfig.SendTraceEvents,
UnrestrictedMode = tenantConfig.UnrestrictedModeEnabled && tenantConfig.UnrestrictedMode,
GatherRules = gatherRules,
- ImeLogPatterns = imeLogPatterns
+ ImeLogPatterns = imeLogPatterns,
+ WhiteGloveSealingPatternIds = adminConfig.GetWhiteGloveSealingPatternIds(),
});
return response;
diff --git a/src/Shared/AutopilotMonitor.Shared/Models/Config/AdminConfiguration.cs b/src/Shared/AutopilotMonitor.Shared/Models/Config/AdminConfiguration.cs
index 97da1224..69adc71f 100644
--- a/src/Shared/AutopilotMonitor.Shared/Models/Config/AdminConfiguration.cs
+++ b/src/Shared/AutopilotMonitor.Shared/Models/Config/AdminConfiguration.cs
@@ -307,6 +307,41 @@ namespace AutopilotMonitor.Shared.Models
}
}
+ // ===== WHITEGLOVE SEALING PATTERN IDS (Plan §M5, M4.4.4 / M4.4.5.e) =====
+
+ ///
+ /// JSON-serialized list of IDs whose match is re-emitted by
+ /// the V2 agent as a WhiteGloveSealingPatternDetected DecisionSignal (in addition
+ /// to the normal ime_pattern_match event). Example: "[\"wg-seal-1\",\"wg-seal-2\"]".
+ ///
+ /// Default null/empty = feature off (M3-compatible, no regression risk). Only IDs in
+ /// this list count as sealing signals; other IME pattern matches follow the regular
+ /// event path. Global-only — no per-tenant override (plan §M5 M4.4.5.e decision).
+ ///
+ ///
+ public string WhiteGloveSealingPatternIdsJson { get; set; } = default!;
+
+ ///
+ /// Returns the deserialized list of WhiteGlove sealing pattern IDs. Falls back to an
+ /// empty list for null/empty/invalid JSON so the feature stays off by default
+ /// (M3-compatible).
+ ///
+ public List GetWhiteGloveSealingPatternIds()
+ {
+ if (string.IsNullOrWhiteSpace(WhiteGloveSealingPatternIdsJson))
+ return new List();
+ try
+ {
+ return JsonSerializer.Deserialize>(WhiteGloveSealingPatternIdsJson,
+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
+ ?? new List();
+ }
+ catch
+ {
+ return new List();
+ }
+ }
+
///
/// Whether vulnerability correlation is globally enabled.
/// When false, agents still collect inventory but backend skips correlation.