Commit Graph

2713 Commits

Author SHA1 Message Date
Joseph Doherty 8013300f64 Merge fix/806-browser-nat-endpoint 2026-11-10 21:24:39 -05:00
Joseph Doherty e6781bc5bc Merge fix/798-shadow-timing-proof 2026-11-10 21:24:38 -05:00
Joseph Doherty 304d1f1e6c test(script-worker): prove Shadow non-blocking causally, not by stopwatch (#798)
ShadowModeTests' runaway case asserted "slowest < 250 ms" around evaluations
whose timing included cold Roslyn compile/JIT of new script text, so it failed
on a healthy published path (540-880 ms measured, both SDK baselines).

The test now puts a test-owned barrier runner (GatedShadow) between the real
ShadowScriptRunner and the real worker pool. The runaway's first shadow run is
held before it reaches the pool, then (after it runs away in the worker, the
pool answers T and the worker is KilledDeadline with the runaway as culprit)
its outcome is held before comparison. Published evaluations made while a
barrier is closed must return the in-process values; a Mirror that waited for
its shadow hangs into a 30 s liveness failure (mutation-checked). Output
correctness, pool kill/quarantine, published-side never quarantined, the single
runaway mismatch (sibling is a victim, not compared) and key_busy drops are kept.

The wall-clock number moves to an opt-in Category=Perf case
(Shadow_published_path_latency_while_the_shadow_runs_away, OTOPCUA_SCRIPT_PERF=1)
on warmed evaluators, reporting hardware and load average.

Sibling: ContainmentTests' hard-deadline case timed the runaway's first
(cold-compile) run; it now compiles the runaway's source first with an input
that does not spin.
2026-11-10 21:15:12 -05:00
Joseph Doherty bd6d3fc2c3 test(historian): Part 13 PercentGood oracle + pin the 1.5.378.182 divergence (#796)
The 1.5.378.182 StatusAggregateCalculator (vendored as the application's
compatibility baseline) counts Uncertain regions as Good time under
TreatUncertainAsBad = false; 1.5.378.106 counted Good regions only, as Part 13
§5.4.3.31/§5.4.3.33 and Mantis 0006166 require. Our evaluator follows Part 13,
so the oracle moved, not the server: 10 SdkAggregateParityTests failed.

SdkAggregateOracle.Run now answers PercentGood through a subclass of the
baseline StatusAggregateCalculator that counts Good regions only (same bounds,
regions, status/Partial bits and configuration as PercentBad). RunSdkAsIs keeps
the untouched baseline. Rejected the proposed 100 - PercentBad under
TreatUncertainAsBad = true: that flag also changes the region machinery (the
end-of-data 1 ms rule), so it computes over a different total; pinned by a test.

New tests pin the divergence on hand-computed G/U/B/G (50 vs baseline 75) and
all-Uncertain (0/0 vs baseline 100) intervals, forward and reverse, plus a
data-starts-inside interval where oracle == baseline.

Sql.Common.Tests 265/275 -> 280/280.
2026-11-10 21:04:27 -05:00
Joseph Doherty 378962eeb1 fix(opcuaclient-browser): keep configured host/port behind NAT (#806)
The AdminUI OPC UA browse picker connected to the EndpointUrl the server
advertised in GetEndpoints verbatim, so behind NAT / a container port
publish it dialled the server's private hostname + internal port and
failed DNS where the runtime driver (which rewrites per #676) connected.

- Move the rewrite to the shared OpcUaEndpointUrlRewriter (.Contracts,
  pure Uri logic, no SDK dependency) and the policy+mode selection to
  OpcUaEndpointSelection (.Session leaf). Driver and browser both select
  through it, so the browse connects exactly where the driver does.
- Policy/mode filter, server certificate and strict browse PKI
  (no auto-accept) are unchanged.
- Unit tests: rewrite (host/port differ, same, IPv6 both sides, path
  kept, port-less, scheme mismatch untouched), browser selection seam.
- Live NAT tests (Fixture=opc-plc-nat, OPCUA_NAT_ENDPOINT): browse via a
  published port with a premise check that the advertised host:port
  differs and does not resolve, nsu= round-trip, unreachable endpoint
  fails cleanly, SignAndEncrypt reaches the server and is refused
  BadCertificateUntrusted. Verified red without the rewrite.

Refs #806
2026-11-10 21:00:04 -05:00
Joseph Doherty f4054f6201 test: enforce late-slope restart and shutdown deadlines 2026-11-10 18:01:41 -05:00
Joseph Doherty 7f21c9aa64 test: capture the preserved late-slope assertion explicitly 2026-11-10 17:50:55 -05:00
Joseph Doherty 5ea6a23f1b test: implement fixed Linux late-slope diagnostic mode 2026-11-10 17:47:31 -05:00
Joseph Doherty 2cf14f403b test: verify SDK2 host disposal ownership and shared completion 2026-11-10 17:16:47 -05:00
Joseph Doherty 58f8d57f5a test: expose concurrent SDK2 host disposal completion race 2026-11-10 17:05:22 -05:00
Joseph Doherty 564f109143 test: align abandoned continuation count across close controls 2026-11-10 16:36:29 -05:00
Joseph Doherty 6737e367b4 test: pin closed authentication token rejection status 2026-11-10 16:33:09 -05:00
Joseph Doherty b5986c61d9 test: preserve bounded cleanup on close control failures 2026-11-10 16:31:05 -05:00
Joseph Doherty 235196beb2 test: add bounded SDK2 close ownership controls 2026-11-10 16:26:26 -05:00
Joseph Doherty 20ad56f3f4 test(runtime): await rebuild outcomes before asserting no-op 2026-11-10 15:22:49 -05:00
Joseph Doherty d7891372f2 test(opcua): prove saturation observer runtime event delivery 2026-11-10 14:55:46 -05:00
Joseph Doherty 54a4ff2149 test(opcua): add fixed candidate saturation diagnosis with observer barriers 2026-11-10 14:55:46 -05:00
Joseph Doherty 67bc61236a Merge remote-tracking branch 'origin/master' into codex/opcua-sdk-2 2026-11-10 14:40:51 -05:00
Joseph Doherty 230cef4ef0 build: adopt HistorianGateway 1.0 TLS options
v2-ci / unit-tests (push) Has been cancelled
2026-11-10 14:40:30 -05:00
Joseph Doherty b57595cb4a test(opcua): distinguish rejected diagnostic samples from passing resource evidence 2026-11-10 13:31:07 -05:00
Joseph Doherty 484f3d3d2d test(opcua): latch rejected profiling budgets through bounded observation 2026-11-10 13:31:07 -05:00
Joseph Doherty 5575e014e7 test(opcua): permit bounded investigative profiling without a full soak claim 2026-11-10 13:31:07 -05:00
Joseph Doherty bfc61b745b test(opcua): retain managed and native growth diagnostics before soak gates 2026-11-10 13:31:07 -05:00
Joseph Doherty 47a29dee2b test: prove peer provenance and repeated alarm authority with baseline scope oracle 2026-11-10 12:50:19 -05:00
Joseph Doherty b9a03b4274 test: repair SDK status oracle and remove duplicate payload cases 2026-11-10 12:50:19 -05:00
Joseph Doherty 642aefe35a test: bound and canonicalize lifecycle descriptor probes 2026-11-10 12:40:41 -05:00
Joseph Doherty bd3143b7d7 fix(client): use default certificate lifetime without rotating persisted keys 2026-11-10 12:26:54 -05:00
Joseph Doherty 41e65f6a22 fix(opcua): restore alarm component reference after SDK creation 2026-11-10 12:14:35 -05:00
Joseph Doherty 8be3bb1734 test(opcua): measure soak observation after startup 2026-11-10 12:14:35 -05:00
Joseph Doherty a456d598ab test(opcua): retain manager references across soak restart 2026-11-10 12:14:35 -05:00
Joseph Doherty 73ee3ef20d test(opcua): add opt-in lifecycle and resource soak harness 2026-11-10 12:14:35 -05:00
Joseph Doherty 1840b5ccf6 test: verify live history authority and secure peer reactivation 2026-11-10 11:53:34 -05:00
Joseph Doherty da50123a2c test: start service-level fixtures with authenticated SDK server 2026-11-10 11:52:09 -05:00
Joseph Doherty a76fca09f6 test: converge historian and runtime fixtures on SDK 2.0 2026-11-10 11:52:09 -05:00
Joseph Doherty 2541ef39f7 test: exercise baseline aggregate vectors through runtime registration 2026-11-10 11:46:29 -05:00
Joseph Doherty dc9e777bff test: prove trusted channels PKI and scoped audit compatibility 2026-11-10 11:43:59 -05:00
Joseph Doherty ab25bd5318 test: preserve historian baseline oracles under SDK 2.0 2026-11-10 11:43:59 -05:00
Joseph Doherty a21104d27c fix(opcua): scope application audit receive permissions to validation 2026-11-10 11:41:31 -05:00
Joseph Doherty 344bc9df8a test(opcua): assert alarm UA type across supported adapters 2026-11-10 11:04:53 -05:00
Joseph Doherty 09ac0e58cb fix(opcua): preserve repeated alarm methods and owned write reverts 2026-11-10 11:04:53 -05:00
Joseph Doherty aac7c88417 test: migrate SDK 2.0 wire contracts and pin baseline aggregate vectors 2026-11-10 10:44:23 -05:00
Joseph Doherty 6aaccb332f fix: initialize real OPC UA client certificate configuration 2026-11-10 10:39:39 -05:00
Joseph Doherty 9cddc9306b test: preserve server SDK 2.0 value security and history contracts 2026-11-10 10:39:39 -05:00
Joseph Doherty 96fd67695f test: verify SDK 2 driver boundaries and lifecycle contracts 2026-11-10 10:23:28 -05:00
Joseph Doherty 33c7dc4292 fix: preserve SDK 2.0 client value and lifetime contracts 2026-11-10 10:20:11 -05:00
Joseph Doherty 83ed880e44 build: bump xunit.v3 to 3.2.2 repo-wide, drop the four VersionOverrides (#795)
v2-ci / unit-tests (push) Has been cancelled
Central pin 1.1.0 -> 3.2.2; AdminUI/ControlPlane/Runtime/Host.Tests no longer carry VersionOverride.
The three driver IntegrationTests fixtures' Fact/Theory attributes gain the source-info ctor (xUnit3003).
No xUnit1051 fallout remained (already cleaned). Test counts unchanged.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 09:31:31 -05:00
Joseph Doherty b211911f98 test: migrate AdminUI/ControlPlane/Runtime/Host tests to xunit.v3 (#756)
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via
VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask,
SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass
TestContext.Current.CancellationToken. Test counts unchanged.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 08:57:33 -05:00
Joseph Doherty 2a8e2981fe feat(scripting): ScriptWorker isolation on Windows: restricted worker identity, pipe ACL, firewall + PeCacheDir installer (lmxopcua#780)
v2-ci / unit-tests (push) Has been cancelled
Owner decisions §14 #5/#6 as built (design §12.5):

- Scripting:Worker:Identity (default unset, Windows only, fail-closed validation): each worker
  starts under a restricted token derived from the host's. Every privilege but SeChangeNotify is
  removed; the restricting SIDs are NT SERVICE\OtOpcUaScriptWorker's SID + Everyone + Users +
  RESTRICTED; the token and default DACLs are host + SYSTEM + identity + RESTRICTED. The worker
  is created suspended, put in its Job Object before it runs, inherits only its stdio pipes, gets
  an ACL'd work dir as TEMP, and runs with DOTNET_EnableDiagnostics=0. The identity is granted on
  the host's window station and desktop, because USER32 init made coreclr.dll fail with
  ERROR_DLL_INIT_FAILED on windev. The logon SID is not used because the host's process DACL
  gives it PROCESS_VM_READ.
- The worker pipe on Windows always has an explicit protected DACL: host account + identity
  only. There is no owner ACE, so an elevated host's Administrators gets no access.
- The worker treats access denied on its parent-pid poll as alive; the Job Object covers that case.
- scripts/install/Install-ScriptWorkerIsolation.ps1 + ScriptWorkerIsolation.psm1 are idempotent
  and support -WhatIf, -Uninstall and -PlanOnly. They create the disabled anchor service, the
  PeCacheDir ACL (protected, no Users, key and markers host-only, pe\ Modify for the identity),
  read+execute on script-worker\, a ReadData deny on the install root, the outbound firewall
  block on the worker exe, and optionally the host-service env.
- Tests: the dry-run harness scripts/install/tests/Test-ScriptWorkerIsolation.ps1 (46
  assertions, mutation-checked); ScriptWorkerIdentityTests; WorkerIsolationTests, which cover
  the pipe ACL, token access and a real restricted worker running a script, all green on windev.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 08:46:23 -05:00
Joseph Doherty 0e96dd08a2 fix(scripting): deny System.AppContext and other process-wide mutators in the script sandbox (lmxopcua#792)
v2-ci / unit-tests (push) Waiting to run
ForbiddenTypeAnalyzer now denies System.AppContext, System.Console, GCSettings, ProfileOptimization,
EncodingProvider/CodePagesEncodingProvider, TypeDescriptor and CryptoConfig as types, and the ambient
CultureInfo.{DefaultThreadCurrentCulture,DefaultThreadCurrentUICulture,CurrentCulture,CurrentUICulture}
and TimeZoneInfo.ClearCachedData members (member deny-list now covers properties and member-access reads).
Shadow-mode tests diverge via culture-sensitive string.Compare (worker is InvariantGlobalization) instead of
AppContext.BaseDirectory, with a premise test. Mutation-proved: 21 new cases fail with the analyzer reverted.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 07:48:39 -05:00
Joseph Doherty 03686eb5d7 feat(scripting): Scripting:Runner=Shadow — in-process results published, each compiled run mirrored to the ScriptWorker pool and compared (lmxopcua#780 P2)
- ShadowScriptRunner (Core.Scripting): IScriptShadow.Mirror copies the inputs and queues the shadow run on the
  pool; compares value (structural), CLR type, status and failure class (compile / budget / threw+message /
  unsupported_type); runner-fault shadow outcomes (Unavailable, worker busy) are not compared. Bounded:
  Scripting:Shadow:MaxInFlight (256) and one per script; drops counted (behind | key_busy).
- Metrics otopcua.script.shadow.{compared,mismatch{script},dropped,inflight}; one rate-limited Warning per
  script and reason with a diff summary (Scripting:Shadow:MismatchLogsPerMinute, re-armed at the seam's deploy).
- The VirtualTag, Calculation and scripted-alarm seams recognise the shadow and keep their in-process path
  byte-for-byte (no coalescer, one-at-a-time pump, in-process engine), mirroring after each compiled run.
- ScriptRunnerSelection builds the shadow over an OutOfProcessScriptRunner whose lines carry
  ScriptRunnerRole=Shadow; Status() is null under Shadow so readiness never sees the shadow pool; the
  script-runner health entry stays Healthy and reports the shadow counters as data.
- docker-dev/docker-compose.script-runner-shadow.yml; docs/VirtualTags.md "Shadow mode".
- Tests: comparison table, per-script metric + once-per-reason log, rate limit, hung/blocking/throwing shadow
  never reaches the caller, MaxInFlight drop accounting, input copy; real-worker divergent + unsupported-type
  scripts counted against exactly those scripts, a worker-only runaway killed/quarantined there with every
  published value intact, alarm transitions identical to in process across a kill -9 of the shadow worker.
  Mutation-proved (16 mutants, 15 red; the one survivor added an assertion).

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 07:23:06 -05:00