diff --git a/docs/deploy/2026-11-13-vd03-fea7de0a-to-6c4d2990.md b/docs/deploy/2026-11-13-vd03-fea7de0a-to-6c4d2990.md
new file mode 100644
index 000000000..dfae113ef
--- /dev/null
+++ b/docs/deploy/2026-11-13-vd03-fea7de0a-to-6c4d2990.md
@@ -0,0 +1,93 @@
+# DEV wonder-app-vd03 upgrade `fea7de0a` → master `6c4d2990` (2026-11-13)
+
+> **Status: EXECUTED 2026-11-13 06:25–06:26Z.** vd03 runs **`6c4d2990`** (`6c4d29902e18d7560ce1ff5607d524a58f674440`).
+> Owner-approved as part of the #700 hybrid decision: deploy a master build carrying the #899/#898 read-only historian
+> binding, then run the remaining #700 checks on the deployed endpoint (record:
+> [`../plans/evidence/2026-11-13-historian-700-vd03/vd03-endpoint-gate.md`](../plans/evidence/2026-11-13-historian-700-vd03/vd03-endpoint-gate.md), second run).
+
+Channel: WinRM + SMB from the Mac (the `winrm` skill), used only for the on-box steps of this deploy (preflight/backup,
+swap, the worker-isolation re-apply). Health, OPC UA and AdminUI checks ran from the Mac over HTTP / opc.tcp. Layout and
+scripts as §(h) of [`vd03-upgrade-b8b4c18f-to-master.md`](vd03-upgrade-b8b4c18f-to-master.md) and the DEV section of
+[`qa-upgrade-b8b4c18f-to-fea7de0a.md`](qa-upgrade-b8b4c18f-to-fea7de0a.md).
+
+**Pinned SHA.** The deploy is pinned to `6c4d2990`. `origin/master` moved to `efaf3435` (#900) during the run; that commit
+is **not** deployed.
+
+## Build
+
+- `dotnet publish src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj -c Release -r win-x64 --self-contained true`
+ on the Mac, in an isolated worktree at `6c4d2990`: **1396 files**, `OtOpcUa.Host.dll` ProductVersion
+ `1.0.0+6c4d29902e18d7560ce1ff5607d524a58f674440`, self-contained `script-worker\`.
+- `deps.json`: Telemetry + Telemetry.Serilog **0.3.1** (not 0.3.0, scadaproj#56), LocalDb 0.3.7, MxGateway.Client 0.4.0,
+ HistorianGateway.Client 1.0.0, Auth 0.4.1, Secrets 0.6.2, Health 0.3.0, Theme 0.5.0.
+- Zip `otopcua-6c4d2990.zip`, SHA-256 `4914D083…FC0850` (full hash in the staged `.sha256`), checked on the box before expanding.
+- `fea7de0a..6c4d2990`: 37 commits, **no ConfigDb migration** (last stays `20261022102459_EnableSnapshotIsolation`), no
+ new required setting. Client-visible on vd03: TimeAverage/Total advertised and served again (#694, `53978ca6`); a
+ Boolean node refuses TimeAverage/Total up front (#897); Telemetry 0.3 sends **logs** over OTLP too, because vd03 has
+ `OtOpcUa__Telemetry__Exporter=Otlp` + `OtlpEndpoint=http://127.0.0.1:4317` (scheme present, so the stricter 0.3
+ endpoint check passes).
+
+## Preflight (stop conditions)
+
+| Check | Result |
+|---|---|
+| **#898 upgrade hazard**: historized Float/Double/Byte/SByte tags that the first start would `EnsureTags` | **None.** Anonymous walk of the deployed address space (raw + UNS, `probe-899 attrs`): 24 variables, **0 with `Historizing=true`**. `/deployments` "In sync" at `dc0af731` (rev `fa9a2e28…`), so draft = deployed. |
+| Continuous / alarm historization | Off: `ContinuousHistorization:Enabled=false`, `AlarmHistorian:Enabled=false` in `appsettings.json`; no `*__Enabled` environment override. `ServerHistorian__Enabled=true` (env). `ServerHistorian__ProvisionTags` absent (default `CreateOrUpdate`); with 0 historized tags nothing can be provisioned. |
+| Shared-lib pins on master | Telemetry already **0.3.1** at `6c4d2990` (`f3178c5f`); no bump needed. |
+| Migrations in range | None. ConfigDb at 9 migrations, last `20261022102459_EnableSnapshotIsolation`, snapshot isolation ON. |
+| Service env | 28 variables (names checked, values not printed): +2 since the fea7de0a record (`OtOpcUa__Telemetry__Exporter`, `OtOpcUa__Telemetry__OtlpEndpoint`). `Scripting__Runner` absent → `InProcess` (unchanged). |
+| Disk | E: 179 GB free. |
+
+## Execution
+
+Staged in `E:\ApiInstall\_stage-otopcua-6c4d2990\` (`prepare.ps1`, `swap.ps1`, `rollback.ps1`, `reisolate.ps1`, zip + `.sha256`).
+
+- **Prepare (service up), `prepare.ps1`:** registry export of the service key to
+ `E:\ApiInstall\OtOpcUa-backups\pre-20261113-otopcua-6c4d2990\OtOpcUaHost.reg` (DACL SYSTEM + Administrators only);
+ zip hash ok, expanded to `E:\ApiInstall\OtOpcUa.new-6c4d2990` (1396 files, ProductVersion checked);
+ `BACKUP DATABASE [OtOpcUaConfig] … COPY_ONLY, CHECKSUM` → `OtOpcUaConfig-pre-20261113-otopcua-6c4d2990.bak`
+ (instance default backup directory, 1913 pages).
+- **Swap, `swap.ps1`** (06:25:50Z): stop (6 s), `OtOpcUa-data` robocopied to
+ `E:\ApiInstall\OtOpcUa-backups\pre-20261113-otopcua-6c4d2990\OtOpcUa-data`, previous install moved to
+ **`E:\ApiInstall\OtOpcUa.bak.20261113-pre-6c4d2990`**, box `appsettings*`, `pki\`, `logs\` carried, start,
+ `/health/ready` 200. **Downtime about 15 s** (stop → ready).
+- **Worker isolation re-applied, `reisolate.ps1`** (lmxopcua#891 arguments, no `-ConfigureHostService`): the folder swap
+ had dropped three ACEs, as `docs/ServiceHosting.md` warns — `WorkerFolderAcl` (`script-worker\`), `InstallRootDeny`
+ (`E:\ApiInstall\OtOpcUa`) and `SensitiveDirDeny` (`…\OtOpcUa\pki`) applied as **changed**; the immediate re-apply
+ reported **every step unchanged**. The runner stays `InProcess`; no restart needed. The same 6 pre-existing
+ "protected DACL" warnings under `E:\psrem-stage` as before.
+
+## Verification
+
+- `/healthz`, `/health/ready` (configdb, akka Up, opcua-endpoint, script runner, deploy coordinator) and `/health/active`: 200.
+- **Running version = target:** the live `E:\ApiInstall\OtOpcUa\OtOpcUa.Host.dll` (SMB copy) carries
+ `1.0.0+6c4d29902e18…`; its SHA256 prefix `6d1f354f674e` equals the published build's.
+- **ServiceLevel 250** (unchanged).
+- **FOCAS:** raw subtree 22 variables, **21 Good**, `Program/BlockCount` `BadNoData` — the same set as before the upgrade.
+- Boot log: `roles=[admin,driver]`, `Historian gateway probe: the gateway is reachable`, **12** aggregates advertised
+ (Average, TimeAverage, Total, …), `applied plan (PureAdd, added=36 …)`, `raw subtree materialised (… tags=22 … failed=0)`,
+ the #815 preflight line, no provisioning line. Pre-existing noise only (Akka `auto-down` deprecation, early
+ `ClusterSingletonProxy` warnings, mesh PSK absent, FQDN-vs-certificate ERR from clients dialling the FQDN).
+- HistorianGateway `/metrics` across the upgrade: `Probe` only, no `EnsureTags` (see the evidence file).
+- Script runner unchanged (`InProcess`, no `Scripting__*` env).
+
+## #700 checks (after the upgrade)
+
+Boolean + Int32 typing and native TimeAverage/Total passed through the deployed endpoint with `historianReadOnly: true`
+bindings, zero historian writes, and the configuration restored byte-identical (rev `fa9a2e28…`, deployment `595cb909`).
+Details: [evidence, second run](../plans/evidence/2026-11-13-historian-700-vd03/vd03-endpoint-gate.md).
+
+## Rollback
+
+`E:\ApiInstall\_stage-otopcua-6c4d2990\rollback.ps1`: stop; move the live folder to `E:\ApiInstall\OtOpcUa.failed-6c4d2990`;
+restore `E:\ApiInstall\OtOpcUa.bak.20261113-pre-6c4d2990` (`fea7de0a`); restore `OtOpcUa-data` from the pre-deploy backup;
+start; re-apply the worker isolation. No schema undo (no migration in range). The ConfigDb `.bak` is there if the
+configuration itself must go back.
+
+## Observed (not deploy blockers)
+
+- Closing an AdminUI tab that had the script editor open logs `[ERR] Unhandled exception in circuit …`
+ `TaskCanceledException` from `MonacoEditor.DisposeAsync` → `SafeInvokeAsync` (the JS runtime is already gone when the
+ circuit disposes). Cosmetic.
+- `/deployments` showed the "In sync" chip on first render after a draft change; it switched to "Configuration drift"
+ after the next interaction.
diff --git a/docs/deploy/qa-upgrade-b8b4c18f-to-fea7de0a.md b/docs/deploy/qa-upgrade-b8b4c18f-to-fea7de0a.md
index 0247934e5..2549fd15c 100644
--- a/docs/deploy/qa-upgrade-b8b4c18f-to-fea7de0a.md
+++ b/docs/deploy/qa-upgrade-b8b4c18f-to-fea7de0a.md
@@ -4,6 +4,9 @@
> `fea7de0a` (from `b8b4c18f`) with the out-of-process script runner on (Calculation + VirtualTag seams) and the
> ScriptWorker isolation installed. Tracking: #801 (SDK 2 live gate), #793 (ConfigDb preflight), #794 (worker
> isolation install), #780 (P3 runner flip). Template: [`vd03-upgrade-b8b4c18f-to-master.md`](vd03-upgrade-b8b4c18f-to-master.md).
+>
+> **DEV vd03 moved on 2026-11-13:** `fea7de0a` → `6c4d2990` (#899 read-only historian bindings; #700 checks). Record:
+> [`2026-11-13-vd03-fea7de0a-to-6c4d2990.md`](2026-11-13-vd03-fea7de0a-to-6c4d2990.md). QA is unchanged.
Channel: WinRM + SMB from the Mac (the `winrm` skill), used only for the on-box steps of this deploy, which the owner
requested on 2026-11-12. Health, OPC UA, ScadaBridge and Overview checks ran from the Mac over HTTP / opc.tcp.
diff --git a/docs/plans/evidence/2026-11-13-historian-700-vd03/gate899-vd03-6c4d2990.json.gz b/docs/plans/evidence/2026-11-13-historian-700-vd03/gate899-vd03-6c4d2990.json.gz
new file mode 100644
index 000000000..fac713708
Binary files /dev/null and b/docs/plans/evidence/2026-11-13-historian-700-vd03/gate899-vd03-6c4d2990.json.gz differ
diff --git a/docs/plans/evidence/2026-11-13-historian-700-vd03/hg-grpc-requests-6c4d2990.txt b/docs/plans/evidence/2026-11-13-historian-700-vd03/hg-grpc-requests-6c4d2990.txt
new file mode 100644
index 000000000..5893f8aa8
--- /dev/null
+++ b/docs/plans/evidence/2026-11-13-historian-700-vd03/hg-grpc-requests-6c4d2990.txt
@@ -0,0 +1,22 @@
+# hgmetrics-0-predeploy
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="Probe",outcome="ok",service="HistorianStatus"} 452 1794551143884
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="GetTagMetadata",outcome="ok",service="HistorianTags"} 5 1794551143884
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadRaw",outcome="ok",service="HistorianRead"} 271 1794551143884
+# hgmetrics-1-postupgrade-predeploy
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="Probe",outcome="ok",service="HistorianStatus"} 464 1794551466108
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="GetTagMetadata",outcome="ok",service="HistorianTags"} 5 1794551466108
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadRaw",outcome="ok",service="HistorianRead"} 271 1794551466108
+# hgmetrics-2-after-temp-deploy
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="Probe",outcome="ok",service="HistorianStatus"} 465 1794551495195
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="GetTagMetadata",outcome="ok",service="HistorianTags"} 5 1794551495195
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadRaw",outcome="ok",service="HistorianRead"} 271 1794551495195
+# hgmetrics-3-after-probe
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="Probe",outcome="ok",service="HistorianStatus"} 465 1794551507122
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="GetTagMetadata",outcome="ok",service="HistorianTags"} 8 1794551507122
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadRaw",outcome="ok",service="HistorianRead"} 283 1794551507122
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadAggregate",outcome="ok",service="HistorianRead"} 6 1794551507122
+# hgmetrics-4-after-cleanup
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="Probe",outcome="ok",service="HistorianStatus"} 471 1794551681614
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="GetTagMetadata",outcome="ok",service="HistorianTags"} 8 1794551681614
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadRaw",outcome="ok",service="HistorianRead"} 283 1794551681614
+historian_gateway_grpc_requests_total{otel_scope_name="ZB.MOM.WW.HistorianGateway",otel_scope_version="1.0.0.0",method="ReadAggregate",outcome="ok",service="HistorianRead"} 6 1794551681614
diff --git a/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Gate899Probe.csproj b/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Gate899Probe.csproj
new file mode 100644
index 000000000..b95f662e6
--- /dev/null
+++ b/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Gate899Probe.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ false
+ false
+
+
+
+
+
diff --git a/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Program.cs b/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Program.cs
new file mode 100644
index 000000000..1a8bf7f86
--- /dev/null
+++ b/docs/plans/evidence/2026-11-13-historian-700-vd03/probe-899/Program.cs
@@ -0,0 +1,224 @@
+// Read-only #700 probe, second vd03 run (master 6c4d2990, read-only historian bindings #899).
+// Sends only Browse/Read/HistoryRead. Credential (gate mode only): env OTOPCUA_PROBE_APIKEY (otua__), never printed.
+// Usage:
+// dotnet run -- attrs anonymous; every Variable in the raw + UNS namespaces with Historizing/DataType
+// dotnet run -- - focas anonymous; ServiceLevel + every Variable under the FOCAS driver
+// dotnet run -- gate apikey; Boolean/Int32/Float typing + native TimeAverage/Total
+using System.Globalization;
+using System.Text.Json;
+using Opc.Ua;
+using Opc.Ua.Client;
+
+var endpointUrl = args[0];
+var outPath = args[1];
+var mode = args[2];
+var key = mode == "gate" ? Environment.GetEnvironmentVariable("OTOPCUA_PROBE_APIKEY") ?? throw new InvalidOperationException("OTOPCUA_PROBE_APIKEY not set") : null;
+var ct = CancellationToken.None;
+var telemetry = DefaultTelemetry.Create(_ => { });
+var pki = Path.Combine(Path.GetTempPath(), $"gate899-probe-pki-{Guid.NewGuid():N}");
+var appConfig = new ApplicationConfiguration(telemetry)
+{
+ ApplicationName = "OtOpcUa.Gate899Probe",
+ ApplicationUri = $"urn:OtOpcUa.Gate899Probe.{Guid.NewGuid():N}",
+ ApplicationType = ApplicationType.Client,
+ SecurityConfiguration = new SecurityConfiguration
+ {
+ ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "own") },
+ TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "issuer") },
+ TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "trusted") },
+ RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "rejected") },
+ AutoAcceptUntrustedCertificates = true,
+ },
+ ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 120_000 },
+};
+await appConfig.ValidateAsync(ApplicationType.Client, ct);
+var endpoint = await CoreClientUtils.SelectEndpointAsync(appConfig, endpointUrl, false, telemetry, ct);
+using var session = await new DefaultSessionFactory(telemetry).CreateAsync(appConfig,
+ new ConfiguredEndpoint(null, endpoint, EndpointConfiguration.Create(appConfig)), false, false, "Gate899Probe", 120_000,
+ key is null ? new UserIdentity() : new UserIdentity("apikey", System.Text.Encoding.UTF8.GetBytes(key)), default, ct);
+
+var rawNs = (ushort)session.NamespaceUris.GetIndex("https://zb.com/otopcua/raw");
+var unsNs = (ushort)session.NamespaceUris.GetIndex("https://zb.com/otopcua/uns");
+
+async Task> Walk(NodeId root)
+{
+ var vars = new List(); var seen = new HashSet(); var queue = new Queue(new[] { root });
+ while (queue.Count > 0)
+ {
+ var b = await session.BrowseAsync(null, null, 0, new[] { new BrowseDescription { NodeId = queue.Dequeue(), BrowseDirection = BrowseDirection.Forward,
+ ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes = true, ResultMask = (uint)BrowseResultMask.All } }, ct);
+ foreach (var r in b.Results.ToList()[0].References.ToList())
+ {
+ var id = ExpandedNodeId.ToNodeId(r.NodeId, session.NamespaceUris);
+ if (id.NamespaceIndex != rawNs && id.NamespaceIndex != unsNs) continue; // skip Server object + ns0 types
+ if (!seen.Add(id)) continue;
+ if (r.NodeClass == NodeClass.Variable) vars.Add(id); else if (r.NodeClass == NodeClass.Object) queue.Enqueue(id);
+ }
+ }
+ return vars;
+}
+async Task