Commit Graph

962 Commits

Author SHA1 Message Date
Joseph Doherty d50433410d feat(health): surface pending-updates counts on the Health dashboard and CLI (ScadaBridge#63)
Health page gains PendingUpdatesHealthTiles (stale instances / sites with stale
artifacts, Deployer+Administrator only, site-scope narrowed, shared cache). New
GetPendingUpdates management command (Deployer) and 'health pending-updates' CLI
leaf. Flatten/cache computation untouched; the command reads
IPendingUpdatesEvaluator directly because the UI cache is not reachable from
ManagementService.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:18:54 -05:00
Joseph Doherty 004bf3cf06 feat(health): show site event-log/audit write and redaction failures on the Health dashboard (ScadaBridge#88)
New SiteWriteFailureBadges component renders warning badges for
SiteEventLogWriteFailures, SiteAuditWriteFailures and AuditRedactionFailure
when > 0 on online sites. The CLI 'health summary' already serialises the full
latest report, so these counters are visible there unchanged.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:13:58 -05:00
Joseph Doherty c29b99c63b perf(templates): descendant reconcile loads only the subtree + chain (ScadaBridge#86)
TemplateService.ReconcileDescendantsAsync - run after every base member
add/update/delete, template and native-alarm-source alike - loaded the whole
tracked template graph (every template, member and script body) on each call,
which after #66 was the dominant cost of those paths. It now calls the new
ITemplateEngineRepository.GetTemplateReconcileClosureAsync: the same tracked,
fully-included shape as GetAllTemplatesAsync, restricted to the edited
template's derived subtree plus its own inheritance chain. The reconcile is
inheritance-only (TemplateInheritanceResolver never follows compositions) and
every descendant's chain runs through the edited template, so that set is
everything it reads.

The walk (Commons TemplateReconcileClosure, the descendant-direction twin of
#66's TemplateAnalysisClosure) loads one children batch per subtree depth and
one batch per ancestor level, terminates on parent cycles and stops at
dangling parents. A template with no children - the common case - costs one
empty probe and loads nothing.

Evidence:
- TemplateReconcileClosureTests: for every template of a graph with a deep
  subtree, an ancestor above the edited template, a composed module,
  look-alike unrelated trees, a parent cycle and a dangling parent, the
  closure-backed reconcile issues exactly the writes (add/update/delete,
  audit, save) of the old whole-graph reconcile, and the resolver answers
  identically for every descendant. Fails with the old service (whole-graph
  load wired to throw) and with the ancestor walk removed (mutation-checked).
- TemplateReconcileClosureRepositoryTests (SQLite, recorded SQL): 5 templates
  / 10 attributes instead of 47 / 94, tracked with script bodies; a leaf costs
  one probe query and tracks nothing.
- TemplateServiceTests: attribute add + update propagate two levels down with
  GetAllTemplatesAsync wired to throw; existing propagation tests now run
  against the closure via ServeReconcileClosureFromFullGraph.

ResyncInheritedMembersAsync (operator action) still reads the whole graph.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:10:31 -05:00
Joseph Doherty 0eac956d25 feat(health): report permanently rejected site audit rows on the site health report (ScadaBridge#69)
SiteAuditBacklogSnapshot gains an optional trailing RejectedCount (gauge of
Rejected forward-state rows, read from the site store), carried on the wire as
SiteAuditBacklogSnapshotDto.rejected_count = 4 (additive; old sites read as 0,
old central ignores it). Health page shows an 'Audit rows rejected' badge.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:05:48 -05:00
Joseph Doherty 3ae3f1bcef refactor(health): move ICentralHealthAggregator and SiteHealthState to Commons (ScadaBridge#71)
Interface goes to Commons.Interfaces.Services and the state record to
Commons.Messages.Health, so CentralCommunicationActor no longer needs the
HealthMonitoring namespace. Implementation stays in HealthMonitoring.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:05:48 -05:00
Joseph Doherty 42443b4659 docs(site-runtime): IsMesReportable and AckTime now ride the wire (ScadaBridge#60, #84)
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 12:55:12 -05:00
Joseph Doherty 6d5bb6189f feat(flow-debugger): carry site-side trace truncation on the route reply (ScadaBridge#54)
ScriptCallResult and RouteToCallResponse gain trailing Truncated/LogsTruncated,
filled from the site FlowTraceCollector after Complete(); RouteToCallResponseDto
fields 8/9 carry them (sitecommand regen). FlowDebugRunner ORs them into
FlowDebugRunResult on the script entry, and on the method entry through a new
optional truncation sink on RouteHelper.WithTraceSink (carried through every
With* clone). Previously a site run whose collector dropped rows came back as a
silently short list. Older sites send false.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 12:37:57 -05:00
Joseph Doherty 5310fd0e26 feat(streaming): carry IsMesReportable on AlarmStateUpdate (25) and DebugAlarmStateDto (20) (ScadaBridge#60)
Central's Debug View live stream and Alarm Summary cache always saw false
because neither wire message had the field. Additive proto fields, vendored
C# regenerated with docker/regen-proto.sh (sitestream + sitecommand), mapped
both ways in StreamRelayActor/SiteStreamGrpcClient and SiteCommandDtoMapper.
The live stream publishes Alarm Actor events, which never carried the flag,
so InstanceActor.HandleAlarmStateChanged now stamps it before retaining and
publishing. A pre-#60 site sends the proto3 default. No UI badge yet.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 12:31:37 -05:00
Joseph Doherty e2b15bd520 fix(communication): evict idle SiteStreamGrpcClientFactory (site, endpoint) clients (ScadaBridge#67)
Each cached client records when it was last handed out; a periodic sweep (half of the new
CommunicationOptions.GrpcClientIdleTtl, default 30 min, 0 disables) disposes and removes entries
unused for the TTL that have no live subscription (SiteStreamGrpcClient.ActiveSubscriptionCount).
Lookup and the sweep's check-and-remove share one lock so a handed-out client is never disposed
under its caller; an evicted client is re-created on next GetOrCreate.
2026-11-09 12:07:28 -05:00
Joseph Doherty a5c3de63e9 fix(dcl): coerce scalar OPC UA writes to the node DataType (#58)
Codec-encoded scalars ("True", "42.5") reached RealOpcUaClient.WriteValuesAsync
as strings and were sent as String Variants, so a Boolean/Double node answered
BadTypeMismatch (0x80740000).

RealOpcUaClient now reads each written node's DataType attribute once per
session (batched, cached, cleared on connect/disconnect; derived types and
enumerations resolved via the supertype chain) and converts non-null scalar
values with the new OpcUaWriteCoercion table: Boolean, SByte..UInt64, Float,
Double, String, DateTime, Guid, all culture-invariant. Overflow, fractional
values to integer types and unparseable strings are refused client-side with
BadTypeMismatch and a message naming the value and target type - never a
rounded value. Arrays, null, unmapped types and nodes whose DataType read
fails pass through unchanged. WriteValueAsync now surfaces the outcome's own
status code instead of always BadNodeIdUnknown. MxGateway path untouched.

Tests: OpcUaWriteCoercionTests (coercion table incl. overflow, invalid and
culture-sensitive strings) and OpcUaWriteCoercionLiveTests (RequiresOpcUa:
String writes to Double/Boolean/UInt32 opc-plc nodes read back typed; reverting
the coercion reproduces 0x80740000). Component-DataConnectionLayer.md updated.
2026-11-06 16:08:33 -05:00
Joseph Doherty 0e89966da6 docs: point markdown backlogs at Gitea issues (status lives in Gitea) 2026-11-06 15:31:44 -05:00
Joseph Doherty 10c04a23d7 docs(deploy): record vd03 + QA rolling upgrade 3808b49f -> 0e540291 (no migration; QA one box at a time, no full outage) 2026-10-22 18:06:26 -04:00
Joseph Doherty 0e5402915b fix(centralui): pending-updates rail refreshes as soon as a deployment finishes — cache drops its snapshot on Success/Failed and signals the indicator, instead of serving the pre-deploy count for up to TTL + one poll 2026-10-22 17:11:18 -04:00
Joseph Doherty 3e1435dfac docs: QA central pair relays standby inbound API calls (StandbyForwardPeerEndpoints) 2026-10-22 16:59:07 -04:00
Joseph Doherty 4bc124d73e docs: QA central pair moved to shared SQL key + secret stores; record the Windows import-rename defect 2026-10-22 15:56:25 -04:00
Joseph Doherty 6ed6800b84 fix(host): clear pooled SQLite handles before renaming the imported inbound-key file — Windows refused the rename, so the import re-ran every boot (QA vq03/vq04 2026-10-22) 2026-10-22 15:56:00 -04:00
Joseph Doherty 5ad6f83176 docs(deploy): record QA wonder-app-vq03/vq04 upgrade b3021e7c -> 3808b49f (migration 58, pair-wise window 2m35s) 2026-10-22 15:38:27 -04:00
Joseph Doherty 51b111a35f docs(deploy): record DEV wonder-app-vd03 upgrade b3021e7c -> 3808b49f (migration 58, gate 31/31) 2026-10-22 15:21:39 -04:00
Joseph Doherty bcd087efe8 feat(centralui): Topology 'Deploy all…' on site and area nodes with confirm dialog, Force and summary toast (Gitea #40) 2026-10-22 14:27:26 -04:00
Joseph Doherty d71f61e71b feat(centralui): Topology pre-loads and expands the first layer (sites -> top-level areas) (Gitea #39) 2026-10-22 14:27:26 -04:00
Joseph Doherty f72c72c52f feat(managementservice): MgmtDeployArea — Deployer-only, scoped by the area's owning site (fail-closed on an unknown area), long-running like MgmtDeploySite (Gitea #40) 2026-10-22 14:27:26 -04:00
Joseph Doherty eb1c9ea7f9 feat(deploymentmanager): DeployAreaAsync bulk-deploys an area subtree through the shared DeploySiteAsync core; PreviewSubtreeDeployAsync returns confirm counts (Gitea #40)
DeploySiteAsync's body moves into a private core taking the site plus an
optional area-id filter; site deploy behaviour is unchanged. The subtree is
one in-memory ParentAreaId walk (cycle-safe) shared by the deploy and the
preview. DeploymentService gains an ITemplateEngineRepository dependency for
the area lookups.
2026-10-22 14:27:26 -04:00
Joseph Doherty 7be22d2ac2 fix(auditlog): truncate over-width SiteCalls.LastError on the cached-telemetry dual-write and the reconciliation pull (review 2026-10-21 follow-up) 2026-10-22 09:58:46 -04:00
Joseph Doherty 89d8568fdf fix(site-runtime): a faulted seed trigger-expression eval adopts nothing instead of replaying as a real false (review 2026-10-21 follow-up L4) 2026-10-22 09:58:46 -04:00
Joseph Doherty d646f816a4 fix(site-runtime): refuse an enable while a deploy pipeline owns the instance; revert is_enabled only for an invalid name (review 2026-10-21 follow-up L3) 2026-10-22 09:58:46 -04:00
Joseph Doherty e60c316f30 fix(dcl): queue a create whose actor name belongs to a still-terminating child instead of refusing it (review 2026-10-21 follow-up L2) 2026-10-22 09:58:46 -04:00
Joseph Doherty 530a096111 fix(auditlog): advance the served-rowid mark under the write lock so a purge clamp is never overwritten stale-high (review 2026-10-21 follow-up L1) 2026-10-22 09:58:46 -04:00
Joseph Doherty 1f87942ce3 fix(auditlog): reconciliation abandons only row-intrinsic failures and only when a sibling row inserted (review 2026-10-21 follow-up M1) 2026-10-22 09:58:46 -04:00
Joseph Doherty 6b8a91cc83 docs(plans): correct the adversarial-catch descriptions in the remediation record 2026-10-22 09:52:01 -04:00
Joseph Doherty 2acd14f276 docs: CLAUDE.md review-remediation facts + 2026-10-21 remediation record 2026-10-22 09:51:52 -04:00
Joseph Doherty b67d736ac9 docs: reconcile stale cluster/security/notification docs; close register row 117, add rows 128-135 (review 2026-10-21 wave 2) 2026-10-22 09:51:52 -04:00
Joseph Doherty 1627513fdb feat(configdb): ReviewRemediationIndexes migration — AuditLog (Kind, Actor) + AuditLogEntries composite indexes
AuditLog indexes are raw SQL ON ps_AuditLog_Month so they stay partition-aligned for the switch purge.
2026-10-22 09:42:02 -04:00
Joseph Doherty 88273693f9 fix(site-call-audit): reconciliation pull serves only un-acked tracking rows (node-local ack ledger), batched page upsert, prune removed sites' cursors (review 2026-10-21 06/PERF-2 + lane H follow-up) 2026-10-22 09:38:48 -04:00
Joseph Doherty e69be1654f fix(kpi-history): fold raw samples one hour at a time; only a unique-key violation is the benign fold race (review 2026-10-21 06/PERF-6, 08/P4, 08/C3) 2026-10-22 09:38:48 -04:00
Joseph Doherty e7fb71c2de fix(site-call-audit): KPI snapshots as single conditional-aggregate queries (review 2026-10-21 06/PERF-3, 08/P3) 2026-10-22 09:38:48 -04:00
Joseph Doherty 811a561885 fix(notification-outbox): per-attempt timestamps, Pending/Retrying split claim + sweep budget, SMTP MaxConcurrentConnections clamp (review 2026-10-21 06/COR-1, 06/PERF-5, 06/IMPL-2) 2026-10-22 09:38:48 -04:00
Joseph Doherty ebf4b96789 fix(notification-service): evict stale OAuth2 token cache entries after credential rotation (review 2026-10-21 06/MEM-3) 2026-10-22 09:38:48 -04:00
Joseph Doherty 7e08a1dbfb fix(transport): import reads the template library once per session, batches instance lookups, and expands stale instances to derived/composing templates over one flatten session (review 2026-10-21 04/P4, 08/P1, 04/C2) 2026-10-22 09:38:17 -04:00
Joseph Doherty 39025b28fa fix(transport): import rejects malformed site gRPC endpoints and non-actor-safe names as blockers, not mid-apply crashes (review 2026-10-21 03/R-1, lane-F ActorNameRules follow-up) 2026-10-22 09:38:17 -04:00
Joseph Doherty 04ac4c0491 fix(transport): export reads instance children once, site connections once per site, composed templates in batches (review 2026-10-21 04/P5) 2026-10-22 09:38:16 -04:00
Joseph Doherty 93fbd2e030 fix(transport): pin a compressed copy of the decrypted bundle on the import session, release the upload buffer early, document the footprint (review 2026-10-21 04/M2) 2026-10-22 09:38:16 -04:00
Joseph Doherty 55cebf2397 fix(transport): zero a bundle session's content when it expires, not only on apply/abandon (review 2026-10-21 04/C3) 2026-10-22 09:38:16 -04:00
Joseph Doherty fad08f7ce7 fix(transport): remove the never-read Transport:SchemaVersionMajor option (review 2026-10-21 04/U1, 08/O3) 2026-10-22 09:38:16 -04:00
Joseph Doherty 52c8a212a1 fix(management): reject native alarm source names that are not legal actor names (review 2026-10-21 lane F follow-up to 01/SR-C1) 2026-10-22 09:32:14 -04:00
Joseph Doherty df1f4efcfa fix(management): invalidate RoleMappingCache after role-mapping and scope-rule mutations commit (review 2026-10-21 lane K M1) 2026-10-22 09:32:14 -04:00
Joseph Doherty d495494074 fix(management): purge terminal PendingSecuredWrites rows on the list sweep after a retention window (review 2026-10-21 08/G3) 2026-10-22 09:32:14 -04:00
Joseph Doherty baab6ac78e fix(management): ListInstances pages, site-scopes and prefix-searches in SQL (review 2026-10-21 08/P2) 2026-10-22 09:32:14 -04:00
Joseph Doherty b0c863c6a1 fix(management): single-flight large /management bodies at the HTTP edge (409 IMPORT_IN_PROGRESS) and parse the command off the request stream (review 2026-10-21 05/M2) 2026-10-22 09:32:14 -04:00
Joseph Doherty 72f96bdbcb fix(management): compile an API method once per save — CompileForPublish replaces the validate pre-pass (review 2026-10-21 05/M1) 2026-10-22 09:32:14 -04:00
Joseph Doherty 36d57f11b3 fix(management): validate site gRPC addresses on create/update (review 2026-10-21 03/R-1) 2026-10-22 09:32:14 -04:00