diff --git a/CLAUDE.md b/CLAUDE.md
index 7d993e6d..a94e758b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -120,6 +120,12 @@ Other peers in the `scadaproj` family (see `scadaproj/CLAUDE.md` for details): `
- DCL write failures returned synchronously to calling script.
- Tag path resolution retried periodically for devices still booting.
- Static attribute writes persisted to local SQLite (survive restart/failover, reset on redeployment).
+- **Consolidated site database (LocalDb Phase 1, 2026-07-19).** `OperationTracking` and `site_events` now live in ONE `ZB.MOM.WW.LocalDb`-managed SQLite file, configured by the **required** `LocalDb:Path` (`/app/data/site-localdb.db` on the rig; validated with `ValidateOnStart`, so a site config missing it fails to boot). Both are `RegisterReplicated` tables. Consequences worth knowing:
+ - `site_events.id` changed from autoincrement INTEGER to an application-minted **GUID**. Last-writer-wins keys on the primary key, so two nodes independently minting `id=1,2,3…` would destroy each other's events rather than merge them. The event-log read path uses a composite `(timestamp, id)` keyset cursor with an **opaque string** continuation token; `EventLogEntry.Id` and both `ContinuationToken`s are `string`/`string?` on the site↔central Akka DTOs.
+ - `ScadaBridge:OperationTracking:ConnectionString` and `ScadaBridge:SiteEventLog:DatabasePath` are **migration-only** — nothing reads them but `SiteLocalDbLegacyMigrator`, which copies a pre-Phase-1 file in once (deterministic `mig-{NodeName}-{legacyId}` event ids, `INSERT OR IGNORE`, runs AFTER `RegisterReplicated` so migrated rows replicate) and renames it `.migrated`. Delete the keys once a node has migrated.
+ - This incidentally fixes a data-loss bug: both legacy databases defaulted to CWD-relative paths **outside** the mounted volume and were discarded on every container recreate.
+ - **Replication is default-OFF and opt-in** via `LocalDb:Replication:PeerAddress` + a matching `ApiKey` on both nodes. `LocalDbSyncAuthInterceptor` is **fail-closed**: no configured key means no sync stream is accepted at all, so a key typo does not degrade to unauthenticated replication — the pair simply stops converging. The sync endpoint shares the existing site gRPC h2c listener (8083); no new port. Rig posture: **site-a replicated, site-b/site-c deliberately not**, so both states are proven side-by-side. Status surfaces on the site health report as `LocalDbReplicationConnected` / `LocalDbOplogBacklog` (both nullable — null means "no data", NOT "disconnected with an empty backlog") and as `localdb_*` Prometheus series. Note `ZbTelemetryOptions.Meters` is an **allowlist** (`SiteServiceRegistration.ObservedMeters`); an unlisted meter exports nothing, silently.
+ - Design: `docs/plans/2026-07-19-scadabridge-localdb-design.md`; Phase 1 plan `docs/plans/2026-07-19-localdb-adoption-phase1.md`. Phase 2 (config tables + `sf_messages`, deleting `SiteReplicationActor` + StoreAndForward `ReplicationService`) is NOT started and needs its own plan.
- All timestamps are UTC throughout the system.
- Inter-cluster communication uses two transports: ClusterClient for command/control (deployments, lifecycle, subscribe/unsubscribe handshake, snapshots) and gRPC server-streaming for real-time data (attribute values, alarm states). Both CentralCommunicationActor and SiteCommunicationActor registered with receptionist. Central creates one ClusterClient per site using NodeA/NodeB as contact points. Sites configure multiple central contact points for failover. Addresses cached in CentralCommunicationActor, refreshed periodically (60s) and on admin changes. Heartbeats serve health monitoring only.
- gRPC streaming channel: SiteStreamGrpcServer on each site node (Kestrel HTTP/2, port 8083); central creates per-site SiteStreamGrpcClient via SiteStreamGrpcClientFactory. Site entity has GrpcNodeAAddress/GrpcNodeBAddress fields. Proto: sitestream.proto with SiteStreamService, SiteStreamEvent (oneof: AttributeValueUpdate, AlarmStateUpdate). DebugStreamEvent message removed (no longer flows through ClusterClient).
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
index 78a9a3d3..483ac83e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
@@ -6,7 +6,14 @@ public class SiteEventLogOptions
public int RetentionDays { get; set; } = 30;
/// Maximum SQLite database size in megabytes before old entries are purged; default 1024 MB.
public int MaxStorageMb { get; set; } = 1024;
- /// File path for the site event log SQLite database.
+ ///
+ /// MIGRATION-ONLY as of LocalDb Phase 1. no longer reads
+ /// this: site_events lives in the consolidated site database at
+ /// LocalDb:Path. The only remaining consumer is
+ /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-Phase-1
+ /// site_events.db and copy it in once. Safe to delete from a config once that
+ /// node's migration has run.
+ ///
public string DatabasePath { get; set; } = "site_events.db";
/// Maximum number of rows returned per paginated query; default 500.
public int QueryPageSize { get; set; } = 500;
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogger.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogger.cs
index a5c6e868..0b95b684 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogger.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogger.cs
@@ -7,9 +7,11 @@ using ZB.MOM.WW.LocalDb;
namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
///
-/// Records operational events to a local SQLite database.
-/// Only the active node generates events. Not replicated to standby.
-/// On failover, the new active node starts a fresh log.
+/// Records operational events into the consolidated site database (LocalDb Phase 1).
+/// Only the active node generates events, but site_events is a replicated table:
+/// when the pair has a peer configured, the standby holds the same history and a failover
+/// no longer starts a fresh log. With no peer configured the database is simply local —
+/// replication is opt-in via LocalDb:Replication:PeerAddress.
///
///
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
index 6ca4c2cc..76979eeb 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
@@ -7,9 +7,12 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tracking;
public class OperationTrackingOptions
{
///
- /// Full ADO.NET connection string for the SQLite database (e.g.
- /// "Data Source=site-tracking.db"). Tests use the
- /// Mode=Memory;Cache=Shared form to keep the database in-memory.
+ /// MIGRATION-ONLY as of LocalDb Phase 1. no longer
+ /// reads this: the tracking table lives in the consolidated site database at
+ /// LocalDb:Path. The only remaining consumer is
+ /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-Phase-1
+ /// site-tracking.db and copy it in once. Safe to delete from a config once that
+ /// node's migration has run.
///
public string ConnectionString { get; set; } = "Data Source=site-tracking.db";