diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
index e943b705..afc500f0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
@@ -27,6 +27,24 @@ namespace ZB.MOM.WW.ScadaBridge.Host;
///
public static class SiteServiceRegistration
{
+ ///
+ /// Every OpenTelemetry is told to observe.
+ ///
+ ///
+ /// ZbTelemetryOptions.Meters is an ALLOWLIST, and an unlisted meter's instruments
+ /// are simply never observed — no error, no warning, no missing-metric signal anywhere.
+ /// The LocalDb replication meter was silently absent from the rig's /metrics
+ /// scrape until the Phase 1 live gate went looking for it (the plan had assumed it
+ /// flowed through automatically). Hoisted to a named constant so the list is
+ /// assertable; adding a meter anywhere in the product means adding it here.
+ ///
+ public static readonly string[] ObservedMeters =
+ [
+ ScadaBridgeTelemetry.MeterName,
+ // Emitted only on site nodes; harmless on central, which never records against it.
+ LocalDbMetrics.MeterName,
+ ];
+
/// Registers all DI services required for the site role.
/// The service collection to register into.
/// Application configuration for options binding.
@@ -262,7 +280,7 @@ public static class SiteServiceRegistration
o.ServiceName = "scadabridge";
o.SiteId = config["ScadaBridge:Node:SiteId"] ?? "central";
o.NodeRole = config["ScadaBridge:Node:Role"];
- o.Meters = [ScadaBridgeTelemetry.MeterName];
+ o.Meters = ObservedMeters;
if (Enum.TryParse(config["ScadaBridge:Telemetry:Exporter"], ignoreCase: true, out var exporter))
o.Exporter = exporter;
var otlp = config["ScadaBridge:Telemetry:OtlpEndpoint"];
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SiteLocalDbWiringTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SiteLocalDbWiringTests.cs
index cff13488..c2d3fc91 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SiteLocalDbWiringTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SiteLocalDbWiringTests.cs
@@ -3,7 +3,9 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
+using ZB.MOM.WW.Telemetry;
using ZB.MOM.WW.LocalDb;
using ZB.MOM.WW.LocalDb.Replication;
using ZB.MOM.WW.ScadaBridge.Host;
@@ -196,6 +198,23 @@ public class SiteLocalDbWiringTests : IDisposable
Assert.Null(report.LocalDbOplogBacklog);
}
+ [Fact]
+ public void Site_Telemetry_Allowlists_TheLocalDbReplicationMeter()
+ {
+ // ZbTelemetryOptions.Meters is an ALLOWLIST: an unlisted Meter's instruments are
+ // never observed, with no error and no missing-metric warning anywhere. The
+ // replication meter was silently absent from the rig's /metrics scrape until the
+ // live gate looked for it, so pin the allowlist rather than trusting the next
+ // reader to remember.
+ //
+ // This asserts on the allowlist constant, not on OTel's observed-meter set:
+ // AddZbTelemetry applies its options to a local instance and never registers
+ // IOptions, so there is nothing resolvable to interrogate. The end-to-end proof
+ // that localdb_* actually reaches /metrics is the Task 12 live gate; this test
+ // exists to stop the entry being dropped again.
+ Assert.Contains(LocalDbMetrics.MeterName, SiteServiceRegistration.ObservedMeters);
+ }
+
[Fact]
public void Site_LocalDb_CreatesTheConfiguredFile()
{