Commit Graph

703 Commits

Author SHA1 Message Date
Joseph Doherty
01509a045f feat(central-ui): add Min time between runs field to the script form
The template script editor had no input for MinTimeBetweenRuns, so a
WhileTrue trigger configured through the UI always saved a null interval
and degraded to a single edge fire. The Add/Edit Script modal now has a
"Min time between runs" number+unit (ms/sec/min) field.

- Visible only for ValueChange / Conditional / Expression triggers — the
  auto-firing triggers MinTimeBetweenRuns throttles. Hidden for Interval
  (its own period is the cadence), Call (invoked explicitly, never
  throttled), and None.
- For a WhileTrue Conditional/Expression trigger the field is labelled as
  the re-fire interval and shows a warning while it is blank.
- Wired through the new-script and edit-script save paths (edit previously
  only preserved the existing value, never let the user change it).

New DurationInput helper does the TimeSpan <-> number+unit conversion;
ScriptTriggerConfigCodec.SupportsMinTimeBetweenRuns classifies trigger
types. Both TDD'd — 21 new tests. CentralUI suite 316 green; verified
end-to-end in the browser (visibility per trigger type, WhileTrue warning,
save/reload round-trip).
2026-05-18 16:44:15 -04:00
Joseph Doherty
437fe154e7 feat(triggers): add WhileTrue fire mode for Conditional/Expression script triggers
Conditional and Expression script triggers gain an optional `mode` field
in their TriggerConfiguration JSON:

- OnTrue (default): unchanged edge/per-change firing. An absent mode field
  parses as OnTrue, so every existing trigger config behaves identically.
- WhileTrue: fires on the false->true edge, then re-fires on a periodic
  timer while the condition holds; stops on the true->false edge. The
  re-fire cadence is the script's MinTimeBetweenRuns; with none configured
  the trigger degrades to a single edge fire and logs a warning.

ScriptActor tracks condition truth state and manages a dedicated
"whiletrue-trigger" timer. ScriptTriggerConfigCodec and ScriptTriggerEditor
round-trip the mode and expose an OnTrue/WhileTrue selector for the two
trigger kinds. Design: docs/plans/2026-05-18-whiletrue-trigger-mode-design.md

Tests: 7 ScriptActor runtime tests (edge fire, timer re-fire, stop,
re-arm, no-MinTimeBetweenRuns degrade, OnTrue regressions) + 14 codec /
editor tests. SiteRuntime suite 206 green, CentralUI suite 295 green.
2026-05-18 10:44:11 -04:00
Joseph Doherty
19870d1f8f docs(plans): design for WhileTrue conditional/expression trigger mode 2026-05-18 10:30:22 -04:00
Joseph Doherty
6139a65a7b fix(site-runtime): fan tag updates out to every attribute sharing a tag path
InstanceActor._tagPathToAttribute was a Dictionary<string,string> — one tag
path mapped to a single attribute. When two attributes reference the same PLC
node (e.g. two composed cooling-tank modules both reading ns=3;s=Tank.Level,
or a pump's TempSensor and AlarmSensor both reading ns=3;s=Sensor.Reading),
SubscribeToDcl's map assignment overwrote, so only the last-registered
attribute ever received values — the rest stayed permanently Uncertain.

The map is now Dictionary<string,List<string>>; HandleTagValueUpdate fans each
update out to every attribute referencing the tag path, and each distinct tag
path is still subscribed only once per connection.
2026-05-18 04:21:26 -04:00
Joseph Doherty
fa7b12c4a3 test(playwright): align E2E nav tests with current NavMenu
The role-navigation and navigation E2E tests asserted on a stale nav model —
labels 'Data Connections', 'Instances', 'Areas' that NavMenu.razor no longer
uses, 'Connections' mapped to /admin instead of /design, and Event Logs /
Parked Messages treated as all-roles when they are Deployment-role gated.
SitesPage_ShowsTable expected an HTML <table> but Sites.razor renders site
cards. Corrected the expectations to the actual NavMenu/Sites markup; the
role-based authorization itself was already correct. Suite: 43/43.
2026-05-18 02:42:44 -04:00
Joseph Doherty
579522c586 fix(security): make auth-cookie SecurePolicy configurable for HTTP-only deployments
The cookie SecurePolicy was hard-coded to Always, so the auth cookie was always
marked Secure and the browser never sent it over plain HTTP — making login
impossible on the HTTP-only Docker dev cluster (login succeeded server-side but
every following request was unauthenticated). Add SecurityOptions.RequireHttps-
Cookie (default true — production stays HTTPS-only); when false the cookie uses
SameAsRequest. The docker/ central nodes set it false.
2026-05-18 02:34:02 -04:00
Joseph Doherty
deedf45676 fix(docker): copy Directory.Packages.props into the build image
The Dockerfile restore stage copied every .csproj but not
Directory.Packages.props, so Central Package Management (adopted in 9c60592)
had no version source inside the container and 'dotnet restore' failed NU1015
('PackageReference items do not have a version specified'). The image could
not be rebuilt since CPM adoption. Copy the props file above the projects.
2026-05-17 06:50:33 -04:00
Joseph Doherty
1038683c58 test(integration): repair IntegrationTests harness and stale API-key test
- ScadaLinkWebApplicationFactory removed the AkkaHostedService SINGLETON, not
  just its IHostedService registration, so IClusterNodeProvider's factory
  (Program.cs) could not resolve it — 10 tests failed at host build. Now removes
  only the factory-registered IHostedService descriptors and keeps the singleton.
- Configure an LDAP service account so ResolveUserDnAsync does search-then-bind
  against GLAuth (whose DN layout the no-service-account fallback DN never
  matched), fixing LoginEndpoint_WithValidLdapCredentials.
- IntegrationSurfaceTests: ApiKeyValidator now matches keys by HMAC hash over
  GetAllApiKeysAsync (ConfigurationDatabase-012); the test mocked the removed
  GetApiKeyByValueAsync path. Suite now 64/64.
2026-05-17 06:46:47 -04:00
Joseph Doherty
adf73ab116 test(host): configure ApiKeyPepper in CentralCompositionRootTests
ConfigurationDatabase-012 made ApiKeyHasher fail fast on a missing/weak HMAC
pepper, so resolving ApiKeyValidator from the central composition root now
requires ScadaLink:InboundApi:ApiKeyPepper to be configured. The composition-
root test's in-memory config now supplies a test pepper, like JwtSigningKey.
2026-05-17 06:36:04 -04:00
Joseph Doherty
722773f2b5 docs(code-reviews): regenerate index — all 66 re-review findings resolved 2026-05-17 05:43:08 -04:00
Joseph Doherty
cfa8667c78 test(central-ui): fix test-host hang in CentralUI.Tests
DiffDialogTests.SetupBodyLockInterop registered bUnit SetupVoid planned
invocations that were never completed; DisposeAsync_WhileOpen awaited
DiffDialog.DisposeAsync -> TryUnlockBodyAsync -> InvokeVoidAsync on one of
them, suspending the test forever so the test host never exited (regression
from the CentralUI-023 catch-narrowing). SetupBodyLockInterop now uses Loose
JSInterop mode. Also dispose the leaked WebApplication instances in the Auth
tests (FileSystemWatcher + ConsoleLoggerProcessor threads) and the extra
ServiceProvider in the DebugView tests. Suite now runs 281 tests in ~7s and
exits cleanly.
2026-05-17 05:43:05 -04:00
Joseph Doherty
e55bd46ca1 fix(health-monitoring): resolve HealthMonitoring-015 — nullable LastReportReceivedAt
A heartbeat-registered site that has never sent a full report now has
LastReportReceivedAt = null instead of the year-0001 sentinel. TimestampDisplay
accepts DateTimeOffset? and renders null as a placeholder ('awaiting first
report') rather than a ~2000-year-stale date. Cross-module: HealthMonitoring +
CentralUI.
2026-05-17 05:43:05 -04:00
Joseph Doherty
7da303d7bb fix(configuration-database): resolve ConfigurationDatabase-012 — store inbound-API keys as HMAC-SHA256 hashes
Inbound-API bearer credentials are no longer persisted in plaintext. ApiKey now
holds a KeyHash (peppered HMAC-SHA256); the key is shown once at creation and
only its hash is stored. Lookup and validation hash the presented candidate.
Cross-module: Commons (ApiKey, ApiKeyHasher), ConfigurationDatabase (mapping +
HashApiKeyValue migration), InboundAPI (ApiKeyValidator), ManagementService
(key creation), CentralUI (ApiKeys.razor). Existing keys must be re-issued.
2026-05-17 05:42:52 -04:00
Joseph Doherty
f23513c30b docs(code-reviews): regenerate index after resolving 64 of 66 re-review findings 2026-05-17 03:18:47 -04:00
Joseph Doherty
d6221419c6 fix(template-engine): resolve TemplateEngine-015,016 — cascade-rename nested derived templates, correct composed-script ParentPath 2026-05-17 03:18:41 -04:00
Joseph Doherty
0135a6b2a6 fix(store-and-forward): resolve StoreAndForward-015..017 — document maxRetries=0 contract, replicate operator retry/discard, real category in activity log 2026-05-17 03:18:41 -04:00
Joseph Doherty
be274212f0 fix(site-runtime): resolve SiteRuntime-017..019 — isolated attribute snapshot for child actors, corrected dispatcher doc, remove dead lifecycle handlers 2026-05-17 03:18:41 -04:00
Joseph Doherty
6d63fef934 fix(site-event-logging): resolve SiteEventLogging-012..014 — fault dropped-event tasks, escape LIKE wildcards, re-triage startup-purge finding (Won't Fix) 2026-05-17 03:18:41 -04:00
Joseph Doherty
a58cec5776 fix(security): resolve Security-012..015 — fail login on partial LDAP outage, escape-aware DN parsing, idle check on refresh, username normalization 2026-05-17 03:18:33 -04:00
Joseph Doherty
f5199e9da9 fix(notification-service): resolve NotificationService-014..018 — classify OAuth2 failures, fail on bad auth config, wire NotificationOptions fallback, disposable concurrency limiter 2026-05-17 03:18:33 -04:00
Joseph Doherty
bf6bd8de5a fix(management-service): resolve ManagementService-014..017 — site-scope enforcement on QueryDeployments, atomic override validation, curated fault messages, test coverage 2026-05-17 03:18:33 -04:00
Joseph Doherty
73a393076a fix(inbound-api): resolve InboundAPI-014..017 — return-value validation, reflection-gateway hardening, deadline-bound routed calls, RouteHelper test coverage 2026-05-17 03:18:33 -04:00
Joseph Doherty
aca65e85bb fix(host): resolve Host-012..015 — consume DownIfAlone in HOCON, sub-second timing precision, config-driven Serilog sinks, transient-only startup retry 2026-05-17 03:18:33 -04:00
Joseph Doherty
eae4077414 fix(health-monitoring): resolve HealthMonitoring-013,014,016 — shorter-timeout cadence, options validation, injected TimeProvider; HealthMonitoring-015 left open (cross-module design decision) 2026-05-17 03:18:24 -04:00
Joseph Doherty
da8c9f171b fix(external-system-gateway): resolve ExternalSystemGateway-015..017 — treat MaxRetries=0 as unset, scope HTTP connection cap to gateway clients, no bare trailing '?' 2026-05-17 03:18:24 -04:00
Joseph Doherty
4fa6f0e774 fix(deployment-manager): resolve DeploymentManager-015..017 — reconciliation applies post-success side effects, updates RevisionHash, corrected XML doc 2026-05-17 03:18:24 -04:00
Joseph Doherty
14ba5495d1 fix(data-connection-layer): resolve DataConnectionLayer-014..017 — real logger for OPC UA client, initial-connect failover, accurate subscribe response, per-tag write-batch results 2026-05-17 03:18:24 -04:00
Joseph Doherty
3d3f43229f fix(configuration-database): resolve ConfigurationDatabase-013,014 — fail-fast on missing key ring, single converter local; ConfigurationDatabase-012 left open (cross-module design decision) 2026-05-17 03:18:24 -04:00
Joseph Doherty
a768135237 fix(communication): resolve Communication-012..015 — endpoint-aware gRPC client cache, address-change recreation, correlation-id validation, node-flip tests 2026-05-17 03:18:17 -04:00
Joseph Doherty
a78c3bcb6f fix(commons): resolve Commons-013,014 — integral JSON index handling, distinguish Malformed vs Legacy OPC UA config 2026-05-17 03:18:17 -04:00
Joseph Doherty
21856a4be7 fix(cluster-infrastructure): resolve ClusterInfrastructure-009,010 — DownIfAlone consumption (via Host-012), validator enforces DownIfAlone=true 2026-05-17 03:18:17 -04:00
Joseph Doherty
d7d74ebe5e fix(central-ui): resolve CentralUI-020..025 — auth-ping idle logout, DebugView race, push-handler disposal guard, JS-interop catch narrowing, claim-constant helper, SessionExpiry tests 2026-05-17 03:18:16 -04:00
Joseph Doherty
f82bcbed7c fix(cli): resolve CLI-014..016 — re-triage update-command contract, doc-surface drift, table-column union 2026-05-17 03:18:16 -04:00
Joseph Doherty
0ba4e49e11 docs(code-reviews): re-review batch 4 at 39d737e — SiteEventLogging, SiteRuntime, StoreAndForward, TemplateEngine
11 new findings: SiteEventLogging-012..014, SiteRuntime-017..019, StoreAndForward-015..017, TemplateEngine-015..016.
2026-05-17 00:51:58 -04:00
Joseph Doherty
3b3760f026 docs(code-reviews): re-review batch 3 at 39d737e — Host, InboundAPI, ManagementService, NotificationService, Security
21 new findings: Host-012..015, InboundAPI-014..017, ManagementService-014..017, NotificationService-014..018, Security-012..015.
2026-05-17 00:48:25 -04:00
Joseph Doherty
89636e2bbf docs(code-reviews): re-review batch 2 at 39d737e — ConfigurationDatabase, DataConnectionLayer, DeploymentManager, ExternalSystemGateway, HealthMonitoring
17 new findings: ConfigurationDatabase-012..014, DataConnectionLayer-014..017, DeploymentManager-015..017, ExternalSystemGateway-015..017, HealthMonitoring-013..016.
2026-05-17 00:45:10 -04:00
Joseph Doherty
e49846603e docs(code-reviews): re-review batch 1 at 39d737e — CentralUI, CLI, ClusterInfrastructure, Commons, Communication
17 new findings: CentralUI-020..025, CLI-014..016, ClusterInfrastructure-009..010, Commons-013..014, Communication-012..015.
2026-05-17 00:41:21 -04:00
Joseph Doherty
39d737ebd6 docs(code-reviews): regenerate index — all low/medium findings resolved 2026-05-17 00:04:56 -04:00
Joseph Doherty
8dd74121c3 fix(inbound-api): resolve InboundAPI-012 — move ParameterDefinition POCO to ScadaLink.Commons (Types/InboundApi) 2026-05-17 00:04:56 -04:00
Joseph Doherty
34588ae10c fix(central-ui): resolve CentralUI-006 — push-based deployment status via IDeploymentStatusNotifier, remove 10s polling timer 2026-05-17 00:02:45 -04:00
Joseph Doherty
a55502254e fix(external-system-gateway): resolve ExternalSystemGateway-011 — name-keyed repository lookups replace fetch-all-then-filter on the call hot path 2026-05-17 00:02:45 -04:00
Joseph Doherty
1e2e7d2e7c fix(central-ui): resolve CentralUI-005 — sliding cookie session expiry (Security AddCookie + AuthEndpoints + SessionExpiry) 2026-05-16 23:54:31 -04:00
Joseph Doherty
b1f4251d75 fix(commons): resolve Commons-008 — replace ValueTuple in SetConnectionBindingsCommand with named ConnectionBinding record (CLI, ManagementService, TemplateEngine, CentralUI) 2026-05-16 23:54:31 -04:00
Joseph Doherty
c583598888 fix(deployment-manager): resolve DeploymentManager-013 — document SMTP-credential artifact handling as a conscious accepted design decision 2026-05-16 23:49:25 -04:00
Joseph Doherty
a2f6c1b9b2 fix(inbound-api): resolve InboundAPI-007 — remove unimplemented Database.Connection() script API from design doc (conflicts with script trust model) 2026-05-16 23:49:25 -04:00
Joseph Doherty
3d0c1c6963 fix(host): resolve Host-002 — REQ-HOST-6 doc corrected to drop unused Akka.Persistence requirement 2026-05-16 23:49:25 -04:00
Joseph Doherty
13a33a6c78 docs(code-reviews): regenerate index after batch 4 low/medium fixes 2026-05-16 22:32:31 -04:00
Joseph Doherty
adb5e75ec3 fix(template-engine): resolve TemplateEngine-011,013,014 — remove dead converter, duplicate-id-safe cycle detection, unified deletion logic; TemplateEngine-012 deferred 2026-05-16 22:32:30 -04:00
Joseph Doherty
9e2416b34c fix(store-and-forward): resolve StoreAndForward-006,007,008,009 — transactional parked reads, PipeTo, fault-isolated activity events; 002/011/012 deferred 2026-05-16 22:32:30 -04:00
Joseph Doherty
dd7626da63 fix(site-runtime): resolve SiteRuntime-012,013,015,016 — doc accuracy, shared LoggerFactory, execution-actor coverage; SiteRuntime-014 deferred 2026-05-16 22:32:30 -04:00