Commit Graph

793 Commits

Author SHA1 Message Date
Joseph Doherty adc8ee4afa test(scriptanalysis): parity test fails on any unmirrored runtime accessor method 2026-06-17 11:09:00 -04:00
Joseph Doherty bee295d3ee fix(central-ui): mirror WaitForAttribute on inbound-script analysis RouteTarget
Add WaitForAttribute(attributeName, targetValue, timeout, cancellationToken)
to InboundScriptHost.RouteTarget and SandboxInboundScriptHost.RouteTarget,
mirroring the shipped runtime signature in RouteHelper. Eliminates the false
CS error the editor raised against valid Route.To("X").WaitForAttribute(...)
calls in inbound API method scripts. Test asserts the call diagnoses clean
under ScriptKind.InboundApi.
2026-06-17 11:04:13 -04:00
Joseph Doherty a1186685a9 fix(scriptanalysis): mirror WaitAsync/WaitForAsync on CompileAttributeAccessor
Adds the four missing overloads (value + predicate × WaitAsync + WaitForAsync)
to CompileAttributeAccessor so template/call scripts that use Attributes.WaitAsync
or Attributes.WaitForAsync pass design-time Roslyn validation.  Covers both root
scope and composed/child scope (Children["x"].Attributes.WaitAsync) automatically
since CompileCompositionAccessor.Attributes already returns CompileAttributeAccessor.
2026-06-17 11:03:24 -04:00
Joseph Doherty c2e89e9d40 fix(central-ui): never render DB connection strings on Integration Definitions list
Connection strings carry credentials; the Database Connections tab rendered the
full string (text + title tooltip) for any Design/Admin user. Replace with a
non-sensitive 'hidden — edit to view' hint so it never reaches the browser DOM.
Connection strings remain editable on the create/edit form. Adds a bUnit
regression guard asserting the seeded secret is absent from the rendered list.
2026-06-17 10:51:18 -04:00
Joseph Doherty af54c8ad11 merge: integrate WaitAsync/M5-audit (parallel session) with galaxy array-write + inbound-timeout fixes 2026-06-17 09:28:15 -04:00
Joseph Doherty 11534089b9 docs(siteruntime): mark WaitAsync deferred items implemented (§3/§4.2/§6) + fast-path throwing-predicate test 2026-06-17 09:15:42 -04:00
Joseph Doherty c482cac110 feat(siteruntime): unpack routed RouteToWaitForAttributeRequest into InstanceActor (spec §6 site half) 2026-06-17 09:10:08 -04:00
Joseph Doherty 61048a4ecf feat(siteruntime): WaitForAsync/WaitResult + quality-gated WaitAsync (spec §3, §4.2) 2026-06-17 09:05:12 -04:00
Joseph Doherty 0f6da8a106 feat(inbound): routed Route.To().WaitForAttribute — contract + central path (spec §6) 2026-06-17 09:02:21 -04:00
Joseph Doherty 04e97f4a87 fix(siteruntime): harden WaitAsync — no spurious match on quality republish, guard throwing predicate, Ask-timeout returns false 2026-06-17 08:44:03 -04:00
Joseph Doherty 75ffa09b8f feat(siteruntime): event-driven Attributes.WaitAsync attribute-change helper
Adds InstanceActor one-shot waiter registry (fast-path + change-match + scheduled
timeout self-eviction), threads per-script timeout token through ScriptRuntimeContext,
and exposes Attributes.WaitAsync(value|predicate, timeout). Replaces handshake busy-poll.
Implements spec docs/plans/2026-06-17-waitfor-attribute-change-helper-spec.md §3-§5;
§6 routed variant + WaitForAsync + quality-only mode deferred.
2026-06-17 08:25:06 -04:00
Joseph Doherty 639e331db1 test+docs(m5): M5.7 — de-date 2 EndToEnd purge tests (closes #52); document T3-T8 in Component-AuditLog/-CLI/README/CLAUDE
Tests: anchor SeedOccurredAt() to a fixed thresholdAnchor (2026-01-20) and compute
RetentionDays dynamically (UtcNow - anchor + 1d) so the threshold always sits near
Jan 20 2026, between the Jan-15 "old" seed (purged) and Apr-15/Jun-15 "kept" seeds.
Seed dates stay within the explicit pf_AuditLog_Month boundary range (Jan 2026 –
Dec 2027) — relative-from-now offsets landed before 2026-01-01 (the catch-all
partition, invisible to GetPartitionBoundariesOlderThanAsync). Both tests confirmed
passing; all 284 AuditLog tests green.

Docs:
- Component-AuditLog.md: per-channel retention overrides (T3, PerChannelRetentionDays
  + bounded DELETE + AuditLogPurge:ChannelPurgeBatchSize); ParentExecutionId tag-cascade
  now spans alarm-triggered + nested CallScript/CallShared + inbound→routed (T4, "no
  further spawn points deferred"); per-node stuck KPIs for Notification Outbox +
  Site Call Audit (T6); T7 structured response-capture increments (request headers in
  Extra.requestHeaders, AuditInboundCeilingHits counter, per-method SkipBodyCapture);
  T8 CLI audit tree; T1 hash-chain + T2 Parquet explicitly marked deferred to v1.x.
- Component-CLI.md + README.md: document audit tree --execution-id <guid> and
  audit backfill-source-node --sentinel/--before/--batch with exact options verified
  against AuditCommands.cs; update Interactions to list new endpoints.
- CLAUDE.md: update audit-log design-decision bullets for T3 per-channel retention,
  T4 tag-cascade complete, T6 per-node KPIs, T7 inbound capture increments, T8 tree
  command; clarify T1/T2 remain deferred to v1.x.
2026-06-16 22:26:09 -04:00
Joseph Doherty 1b63d6751f fix(audit): M5 integration — add BackfillSourceNodeAsync to 5 test stubs (M5.5+M5.6 interface collision after cherry-pick recovery) 2026-06-16 22:11:10 -04:00
Joseph Doherty 50b674accc feat(audit): M5.5 per-channel retention overrides via purge-role bounded delete (T3) 2026-06-16 22:05:08 -04:00
Joseph Doherty 55630b48b6 feat(audit): M5.6 SourceNode sentinel backfill (purge-role) + CLI + runbook note (T5) 2026-06-16 22:02:21 -04:00
Joseph Doherty d4ec84d5fb fix(inbound): log swallowed scope-creation failure + test scope disposal on script throw 2026-06-16 22:00:10 -04:00
Joseph Doherty daff1446d8 feat(inbound): expose read-only Database helper on InboundScriptContext 2026-06-16 22:00:10 -04:00
Joseph Doherty 16fc62bfa0 test(inbound): add namespace + Query() coverage for InboundDatabaseHelper 2026-06-16 22:00:10 -04:00
Joseph Doherty 90ac746fdc feat(inbound): read-only InboundDatabaseHelper for inbound scripts 2026-06-16 22:00:10 -04:00
Joseph Doherty 20760014c2 feat(audit): M5.4 ParentExecutionId tag-cascade for alarm + nested calls (T4) 2026-06-16 21:42:14 -04:00
Joseph Doherty 209f368cb5 feat(audit): M5.2 per-node stuck-count KPIs (T6) — repo per-node aggregation, actor message pair, CentralUI tiles 2026-06-16 21:34:14 -04:00
Joseph Doherty a07ff28f10 feat(audit): M5.3 response-capture increments — request headers, ceiling-hits counter, per-method body opt-out (T7)
1. Request headers in Extra JSON (AuditWriteMiddleware): adds a `requestHeaders`
   object to the existing Extra JSON alongside remoteIp/userAgent; headers whose
   names appear in AuditLogOptions.HeaderRedactList (Authorization, X-Api-Key,
   Cookie, Set-Cookie by default) are replaced with "<redacted>" using
   OrdinalIgnoreCase matching — same policy as ScadaBridgeAuditRedactor.

2. AuditInboundCeilingHits counter: new IAuditInboundCeilingHitsCounter interface
   + NoOpAuditInboundCeilingHitsCounter default; AuditCentralHealthSnapshot
   implements the interface (Interlocked field, thread-safe) and exposes
   AuditInboundCeilingHits on IAuditCentralHealthSnapshot; AddAuditLog registers
   the NoOp default, AddAuditLogCentralMaintenance forwards to the snapshot;
   AuditWriteMiddleware accepts the counter as an optional ctor arg and increments
   it once per request where either the request or response body hit the cap.

3. Per-method SkipBodyCapture opt-out: adds SkipBodyCapture bool to
   PerTargetRedactionOverride; AuditWriteMiddleware consults the per-target
   override map at the start of InvokeAsync (before EnableBuffering) and, when
   set, skips body read + capture entirely — the audit row still emits with
   headers/metadata but null RequestSummary/ResponseSummary; truncation flags
   are also cleared so the ceiling-hits counter is not bumped for opted-out methods.
2026-06-16 21:23:07 -04:00
Joseph Doherty 0569c5ff23 feat(audit): M5.1 audit tree endpoint + CLI audit tree (T8)
Add GET /api/audit/tree endpoint that accepts executionId query param,
authenticates via HTTP Basic + LDAP (OperationalAudit permission), calls
IAuditLogRepository.GetExecutionTreeAsync, and returns a JSON array of
ExecutionTreeNode. Returns 400 for missing/invalid GUID, 401/403 as normal.

Add `scadabridge audit tree --execution-id <guid> [--format table|json]`
CLI subcommand in AuditCommands.Build(). Adds AuditTreeHelpers with:
  - BuildUrl: constructs the relative URL + query string
  - RunTreeAsync: calls the endpoint, dispatches to table or JSON renderer
  - WriteTable: indented ASCII tree (root → children, [*] marks queried node)
  - WriteJson: pretty-printed JSON array pass-through

Tests: 7 new ManagementService endpoint tests (valid id, empty, 400, 401,
403, Viewer allowed, wrong role), 18 new CLI tests (parse, render, HTTP
error codes, JSON output, multi-level indentation, queried-node marker).
2026-06-16 21:20:54 -04:00
Joseph Doherty 9106efafd8 Merge main (DCL alarm fixes 06ef177..9b78e60) into M3 branch 2026-06-16 20:20:27 -04:00
Joseph Doherty 069757209a fix(scriptanalysis): M3.6 — full-framework analysis refs close forbidden-type-in-allowed-ns blind spot; pin Process/Stopwatch; fix stale codec test; drop dead ContainsInCode 2026-06-16 20:00:28 -04:00
Joseph Doherty 9b78e6071d fix(dcl): identify MxGateway native alarms by object-relative reference
Surface native (Galaxy/MxGateway) alarms by their object-relative reference
(e.g. "Z28061.HeartbeatTimeoutAlarm") instead of the gateway's full provider
reference ("Galaxy!<area>.<object>.<alarm>"). The area is already preserved in
Category and the object reference is globally unique within the galaxy, so the
full provider prefix added only noise to the alarm identity operators see.

MxGatewayAlarmMapper.MapTransition/MapSnapshot now set SourceReference from
SourceObjectReference, falling back to AlarmFullReference only when the gateway
omits the object reference. +2 mapper tests; full DCL suite green (158).
2026-06-16 19:46:44 -04:00
Joseph Doherty cf935d5744 refactor(centralui): M3.5 ScriptAnalysisService uses shared deny-list + delegates trust verdict 2026-06-16 19:40:03 -04:00
Joseph Doherty 64d6ac7288 refactor(siteruntime): M3.3 ValidateTrustModel delegates to shared ScriptAnalysis + compile-surface parity test 2026-06-16 19:37:50 -04:00
Joseph Doherty 14bd25196a feat(templateengine): M3.2 deploy gate delegates to shared ScriptAnalysis (real compile + authoritative forbidden-API) 2026-06-16 19:36:03 -04:00
Joseph Doherty 784fee7b07 refactor(inboundapi): M3.4 ForbiddenApiChecker delegates to shared ScriptAnalysis validator 2026-06-16 19:35:43 -04:00
Joseph Doherty 361e7f41ba fix(dcl): broadcast SnapshotComplete sentinel to all alarm subscribers
The MxGateway alarm mapper emits the SnapshotComplete framing sentinel with
empty SourceReference/SourceObjectReference. HandleAlarmTransitionReceived
routed every transition by prefix match against the subscriber's source, so
the empty-ref sentinel ('' .StartsWith("<src>.") == false) was dropped for
any specific source. The NativeAlarmActor buffers snapshot conditions and only
flushes them on SnapshotComplete, so statically-active native alarms delivered
only in the initial snapshot (no later live transition) never surfaced.

Broadcast the SnapshotComplete sentinel to all alarm subscribers (bypassing the
source match + type filter) so each NativeAlarmActor's snapshot swap completes.
Adds a regression test using the real empty-ref sentinel against a specific
(prefix) source.
2026-06-16 19:33:41 -04:00
Joseph Doherty 069c0e0b1a fix(scriptanalysis): M3.1 review — Pass 2 self-sufficient descent, pin nested-forbidden + nameof cases, drop dead code 2026-06-16 19:29:59 -04:00
Joseph Doherty 4f2b17ce6d feat(scriptanalysis): M3.1 shared trust validator + compiler + compile surfaces + tests 2026-06-16 19:18:39 -04:00
Joseph Doherty 06ef1779bd fix(dcl): deliver initial-read seed value after subscription registration
DataConnectionActor seeded a tag's initial value by Tell-ing TagValueReceived
from HandleSubscribe's background task, which runs BEFORE HandleSubscribeCompleted
registers the instance's tags in _subscriptionsByInstance. HandleTagValueReceived's
fan-out then found no subscriber and dropped the value. A tag that soon gets a
data-change notification recovers, but a STATIC tag (e.g. an idle MES field that
never changes) was left Uncertain forever — the dropped seed was its only value.

Seeds now ride back on SubscribeCompleted and are delivered after registration,
reusing HandleTagValueReceived's generation guard, fan-out and quality accounting.
+1 regression test (DCL026).
2026-06-16 18:42:28 -04:00
Joseph Doherty 33af948651 Merge feature/native-typed-json: native-typed JSON for List attribute values + data normalization
List values now encode as native-typed JSON ([10,20], [true,false], ISO dates;
strings stay quoted) via AttributeValueCodec; Decode reads both native and the
earlier array-of-strings form for every element type. Already-persisted old-form
data is normalized on the fly: idempotent central startup normalizer
(ListValueNormalizer), active site-SQLite normalization on InstanceActor
override-load, and normalize-on-import in the bundle importer. Instance-override
writes now stamp ElementDataType (#93/M3). Full solution 0/0; feature-targeted
tests green. Plan: docs/plans/2026-06-16-native-typed-json.md.
2026-06-16 18:36:07 -04:00
Joseph Doherty f4b101b532 feat(db): idempotent startup normalizer rewriting List values to native JSON 2026-06-16 17:50:19 -04:00
Joseph Doherty e3d804a1a6 feat(transport): normalize List attribute values to native JSON on import 2026-06-16 17:50:05 -04:00
Joseph Doherty 5841cec958 feat(siteruntime): normalize old-form List static overrides to native JSON on load 2026-06-16 17:49:21 -04:00
Joseph Doherty bf80ca1388 test(commons): NJ-1 review — backward-compat decode tests for old-form Float/DateTime + assert DateTime list is quoted-string array 2026-06-16 17:38:57 -04:00
Joseph Doherty abe8832e9e feat(template): stamp ElementDataType on instance attribute overrides
Set existingOverride.ElementDataType and newOverride.ElementDataType from
templateAttr.ElementDataType in both the update and create branches of
SetAttributeOverrideAsync, so the persisted InstanceAttributeOverride row
always carries the element type for later central normalizer use (#93/M3).
2026-06-16 17:33:15 -04:00
Joseph Doherty 180d55482b feat(commons): native-typed JSON for List values; Decode reads both forms 2026-06-16 17:32:40 -04:00
Joseph Doherty d312dfb139 fix(management): honor DisableLogin on the Basic-Auth CLI surfaces
DisableLogin only swapped the cookie auth scheme (AutoLoginAuthenticationHandler),
which covers the interactive UI. The CLI authenticates POST /management, the audit
REST endpoints, and the SignalR debug-stream hub with HTTP Basic, and each ran its
own hardcoded Basic->LDAP check that ignored DisableLogin. In a login-disabled (e.g.
no-LDAP) deployment that locked the CLI out: every call returned 401 AUTH_FAILED.

Add ManagementAuthenticator, which centralizes the management/CLI auth flow:
when ScadaBridge:Security:Auth:DisableLogin is true it synthesizes the same dev
principal as AutoLoginAuthenticationHandler (configured user, all roles, system-wide)
and bypasses Basic->LDAP; otherwise the unchanged Basic->LDAP flow runs. Wired into
ManagementEndpoints (delegates), AuditEndpoints (delegates), and DebugStreamHub
(bypass branch). +6 unit tests; ManagementService.Tests green (140).
2026-06-16 17:12:17 -04:00
Joseph Doherty 94be5e813b fix(siteruntime): decode List value to typed array before DCL write (OPC UA array write path) 2026-06-16 16:48:28 -04:00
Joseph Doherty ae2e1efb1c feat(ui): List attribute override editor in InstanceConfigure
When overriding a List attribute, render the shared AttributeListEditor
(whole-list replacement; element type fixed by the base, shown read-only via
ShowElementType=false) instead of the single-line input. Loading an existing
override decodes its JSON into rows (malformed -> empty); saving encodes rows to
canonical JSON with a pre-submit Decode round-trip guard surfacing element
errors inline. Clearing removes the InstanceAttributeOverride row
(repository-direct, mirroring native-alarm-source overrides). Non-List override
UX unchanged.
2026-06-16 16:25:58 -04:00
Joseph Doherty ba7331e67c feat(ui): List attribute editor in TemplateEdit 2026-06-16 16:20:08 -04:00
Joseph Doherty 85db4571b2 feat(cli): --element-type and JSON --value for List attributes 2026-06-16 16:18:08 -04:00
Joseph Doherty 0164f8a0d6 fix(mgmt): MV-10 review fixes (ElementDataType fixed-field in LockEnforcer; graceful bad-DataType error; message consistency) 2026-06-16 16:13:38 -04:00
Joseph Doherty 1525670fe7 feat(mgmt): accept + validate ElementDataType on attribute add/update 2026-06-16 16:05:05 -04:00
Joseph Doherty ad6bfc8af9 fix(siteruntime): reject SetStaticAttribute with malformed list value (no silent poison persist) 2026-06-16 15:59:30 -04:00
Joseph Doherty 7f97780bb3 feat(siteruntime): decode static List attributes to typed lists in InstanceActor (load/override/set) 2026-06-16 15:52:29 -04:00