Commit Graph

3505 Commits

Author SHA1 Message Date
Joseph Doherty e280db3753 fix(runtime): comms_latched_refs meter tag key 'driver' -> 'driver_instance' — the R3-04 bare-role-literal guard caught the R3-02 tag key at merge (cross-branch collision; neither branch saw the other) 2026-08-20 11:55:16 -04:00
Joseph Doherty 157d01c2c2 Merge branch 'fix/r3-02-stale-good-family' — R3-02: one quality contract at four seams — comms loss marks raw+UNS value variables BadNoCommunication (WriteStatuses sink member, recovery audit), Calculation + VT hold on Bad / worst-of sub-code / (value,status) dedup, MQTT initial push + ConnectionLost stale, Sparkplug birth replay (archreview 03/S15, 05/STAB-19, 05/STAB-18, 02/S14) 2026-08-20 11:49:06 -04:00
Joseph Doherty 886082fec7 fix(review): calc ReadOne reports an inputs-Bad publish; VT inputs-bad counter ticks only on a real report 2026-08-20 11:48:41 -04:00
Joseph Doherty 74794f4365 docs(archreview): R3-02 execution notes — T18 sweep + outstanding live gate 2026-08-20 11:34:18 -04:00
Joseph Doherty 93704f64ab docs(archreview): R3-02 status 2026-08-20 11:33:56 -04:00
Joseph Doherty 62c5bc3aad fix(runtime): a stopping driver child announces comms loss so restart/respawn latches its nodes Bad 2026-08-20 11:07:02 -04:00
Joseph Doherty f1b976f5e2 Merge branch 'fix/r3-04-one-role-authority' — R3-04: EffectiveRoles is the one role authority (Cluster:Roles wins over OTOPCUA_ROLES, normalised, validated, fail-start on unknown/overlay-changed roles); Program.cs + all four validators consume it; case-insensitive gossip predicates; source-scan guard (archreview 01/S-12 = 03/C5, 01/C-9) 2026-08-20 11:05:09 -04:00
Joseph Doherty e6a36f4ba8 docs: ServiceHosting.md overlay-filename casing note; R3-04 review outcome (both FIX-FIRST items resolved) 2026-08-20 11:03:24 -04:00
Joseph Doherty 1d251cc037 Merge branch 'master' into fix/r3-02-stale-good-family
# Conflicts:
#	src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs
#	tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Harness/V3ArtifactSeeder.cs
2026-08-20 11:03:02 -04:00
Joseph Doherty af39e61638 fix(cluster): T7's post-overlay guard was structurally unreachable — compare live Cluster:Roles, not the PostConfigured options.Roles (review finding, R3-04)
options.Roles is unconditionally overwritten by AddOtOpcUaCluster's PostConfigure to equal
EffectiveRoles.Gossiped (T2's own structural-identity fix), so comparing options.Roles against
Gossiped could never observe a divergence — an overlay setting Cluster:Roles was silently
ignored, not fail-start. The validator now injects IConfiguration and re-reads the raw
Cluster:Roles section LIVE (reflecting any overlay applied after EffectiveRoles was first
resolved), normalized through EffectiveRoles.Resolve for reuse of its own validation.

AddOtOpcUaCluster now TryAddSingleton(configuration) so a bare ServiceCollection test harness
(not just WebApplicationBuilder) can supply IConfiguration to the validator.

Both guard tests rewritten to go through the real AddOtOpcUaCluster + PostConfigure pipeline
over a live ConfigurationManager, simulating an overlay as a second provider added AFTER
EffectiveRoles was resolved off the same object — exactly Program.cs's own sequencing. Confirmed
red beforehand: the "changes" test failed to throw against the pre-fix validator.
2026-08-20 11:01:33 -04:00
Joseph Doherty 5dc8d34472 docs: the stale-Good quality contract, stated once (R3-02) 2026-08-20 10:56:16 -04:00
Joseph Doherty e3a72dc385 fix(runtime): virtual tags hold on Bad inputs and inherit the worst input status (archreview 02/S14) 2026-08-20 10:53:26 -04:00
Joseph Doherty 3648d3c129 fix(cluster): ClusterRoleInfo._roleLeaders stays Ordinal — Akka tracks role-leadership per exact gossiped role string (review finding, R3-04) 2026-08-20 10:52:51 -04:00
Joseph Doherty cc9b845f99 feat(engines): IVirtualTagEvaluator snapshot overload; Roslyn read cache stops fabricating Good (02/S14) 2026-08-20 10:45:11 -04:00
Joseph Doherty d79d51dd4f Merge branch 'fix/r3-01-mesh-grpc-allowanonymous' — R3-01: mesh gRPC + LocalDb sync endpoints opt out of the AdminUI cookie FallbackPolicy (PSK interceptors are the gate); real-auth-stack boundary tests + map-convention guard; rig DisableLogin parameterised (archreview 03/S14 Critical) 2026-08-20 10:43:49 -04:00
Joseph Doherty c8cf5c8032 test(host): the R3-01 falsifiability control asserts the cookie-challenge signature (a bare Exception was satisfiable by an anonymised endpoint too); docs wording 2026-08-20 10:43:49 -04:00
Joseph Doherty 60e5b40228 fix(calculation): read cache carries real input status + timestamps 2026-08-20 10:40:15 -04:00
Joseph Doherty 851e4cf5e0 Merge branch 'fix/r3-03-restart-resubscribe-watchdog' — R3-03: SpawnChild always sends the desired set (operator Restart + supervision restart re-provision, stale-alarm-set re-subscribe, incarnation check, 10 s backoff); watchdog grace = max(LastSuccessfulRead, connectedSince) (archreview 03/S16, S17, U6) 2026-08-20 10:40:11 -04:00
Joseph Doherty 6d0002889a docs(archreview): R3-03 review outcome — F1/F2/F3 fixed with red-before evidence 2026-08-20 10:39:47 -04:00
Joseph Doherty 0793b0cba8 fix(runtime): rate-limit the supervision reprovision — deferred, not dropped (review F3)
The default decider restarts a faulting child immediately and without limit, and
the hook makes every restart do ShutdownAsync+InitializeAsync+SubscribeAsync, so a
driver faulting on every value became a reconnect storm. Per-driver last-reprovision
stamp + DefaultReprovisionBackoff (10s, matched to DefaultReconnectInterval);
a held request is re-delivered by a single per-driver timer carrying the original
incarnation ref, so the F2 identity check still applies.

Seen RED with the backoff disabled: InitializeCount == 3 (second applied
immediately) where the test requires 2-then-3.

Also: the _children.Keys union-arm test (seeder gained taglessDrivers) and the
_desiredByDriver xmldoc note that it lingers on last-known-good after a failed apply.
2026-08-20 10:39:13 -04:00
Joseph Doherty 48efdac600 fix(calculation): hold evaluation on Bad inputs and propagate the worst input status; status-keyed dedup (05/STAB-19) 2026-08-20 10:37:29 -04:00
Joseph Doherty 027c423502 docs(plans): R3-04 execution notes — T6-T12 deviations recorded, T13 marked pending (live gate deferred to orchestrator) 2026-08-20 10:33:07 -04:00
Joseph Doherty 985de0b6ad chore(docker-dev): site-b-2 is Cluster:Roles-only, site-a-2 pins a miscased Cluster:Roles entry (archreview 01/S-12, 01/C-9)
T11: site-b-2 drops OTOPCUA_ROLES entirely — Cluster:Roles is its sole role source, the rig's
only node that can exhibit the two-role-authority split (01/S-12). Expected to fail every
deploy on a pre-R3-04 build.

T12: site-a-2 pins Cluster__Roles__0="Driver" (capital D) as the standing 01/C-9 case-mismatch
pin, keeping cluster-SITE-A verbatim. Pre-fix this un-elects the SITE-A Primary.

Combined into one commit (deviation from the plan's two separate T11/T12 commits): the header
comment block documenting all six nodes' role sources interleaves both nodes on adjacent lines,
so a clean split was not practical. Recorded in the plan's Execution notes.

Compose edits only — the rig is NOT brought up as part of this change.
2026-08-20 10:32:18 -04:00
Joseph Doherty 3f8d5815b3 fix(runtime): mux adapter forwards the dependency's real status code (05/STAB-19) 2026-08-20 10:30:14 -04:00
Joseph Doherty b8d2d50006 docs: one role authority — precedence, normalization, and the overlay invariant (R3-04) 2026-08-20 10:29:37 -04:00
Joseph Doherty b725788954 docs(plans): R3-01 execution notes (T1-T9) - filter gotcha, T4/T5 fixture scope, observed cookie-challenge signature 2026-08-20 10:28:55 -04:00
Joseph Doherty ae97b32c82 test(runtime): comms-loss → identical-value recovery contract + silent-resubscribe control (03/S15) 2026-08-20 10:27:48 -04:00
Joseph Doherty b8911eaa14 docs(plans): mark R3-01 T9 done; T10 remains pending (live gate, orchestrator-run) 2026-08-20 10:27:45 -04:00
Joseph Doherty a222421374 docs: mesh gRPC ingress is AllowAnonymous; the PSK interceptors are the gate (R3-01)
New docs/security.md section "Mesh gRPC ingress is AllowAnonymous by design"
(archreview 03/S14): what the cookie FallbackPolicy covers, why it can block a
correctly-PSK'd call before the interceptor's verdict matters, why the
AllowAnonymous opt-out is safe (all five interceptors fail-closed), the
affected-endpoint table (fused-vs-driver-only distinction), and the two
standing guards (MeshGrpcAnonymousMetadataTests, MeshGrpcMapConventionTests)
plus the auth-stack/driver-only boundary tests.

One cross-reference line each in docs/Configuration.md (§MeshTransport,
§ConfigSource/ConfigServe) and docs/Telemetry.md, so an operator reading
about the listener ports meets the constraint where they'd look for it.
2026-08-20 10:27:40 -04:00
Joseph Doherty 2b7100cf19 fix(runtime): route a reprovision by incarnation, not driver id (review F2)
A ReprovisionRequested in flight from an old incarnation could reach the NEW child
after HandleRestartDriver swapped it in. In Connecting that child DOES handle
InitializeRequested, so it started a second concurrent InitializeAsync, racing the
#527 dispose-before-overwrite.

Seen RED: InitializeCount == 2 on a child held in Connecting by a gated init
(an already-connected child swallows InitializeRequested and passes vacuously).
2026-08-20 10:27:02 -04:00
Joseph Doherty 659d5f51e4 test(integration): a Cluster:Roles-only node wires the driver stack 2026-08-20 10:26:34 -04:00
Joseph Doherty 4dde5cb8bf docs(plans): mark R3-01 T7 and T8 done 2026-08-20 10:25:12 -04:00
Joseph Doherty f3832aa48c test(host): a mesh gRPC map added outside MeshGrpcEndpoints fails the build
MeshGrpcMapConventionTests - source-scan of Program.cs (solution-root walk,
following FlushLoggingConventionTests.cs's idiom): zero direct
MapGrpcService</MapZbLocalDbSync( occurrences, exactly one
MapOtOpcUaMeshGrpcServices( call.

Complements the endpoint-metadata census (T1-T3): the census catches a map
added to MeshGrpcEndpoints without the AllowAnonymous convention, but cannot
see a map that bypasses the helper and goes straight into Program.cs - that
shape never reaches MapOtOpcUaMeshGrpcServices at all. Verified red against
the pre-T1 Program.cs (five inline map sites, zero mapper calls) and green
against the current extracted form.
2026-08-20 10:25:02 -04:00
Joseph Doherty edd01fcc34 fix(runtime): a changed alarm set re-subscribes instead of latching (review F1)
The alarm subscribe gated on 'a handle exists', which is correct for an idempotent
re-push and wrong for a deploy that widens/narrows the set. Combined with the
spawn-time desired set, a respawning deploy could latch the PREVIOUS revision's
alarm refs for the life of the connection; sourceNodeIds-filtering drivers
(OpcUaClient/AbCip/Focas) would never deliver the new alarm.

Seen RED: LastSubscribedAlarmRefs stayed [alarmX] after a deploy widening to
[alarmX, alarmY].
2026-08-20 10:24:52 -04:00
Joseph Doherty 82ad98e1a9 chore(docker-dev): parameterise Security__Auth__DisableLogin on the central pair
Security__Auth__DisableLogin: "${OTOPCUA_DISABLE_LOGIN:-true}" on central-1
and central-2 - byte-identical default behaviour, verified via `docker
compose config` both unset (resolves "true") and with
OTOPCUA_DISABLE_LOGIN=false (resolves "false").

DisableLogin's always-succeeding handler, registered under the cookie scheme
name, masks the AdminUI cookie FallbackPolicy entirely - which is exactly how
03/S14 (the FallbackPolicy blocking every mesh gRPC call on a login-enabled
admin node) survived seven phases of prior live gates that all ran with the
default. The production-faithful mode is now a one-variable flip instead of
an edit-and-remember-to-revert; header comment names the flip and the LDAP
users it needs (archreview 03/S14, R3-01 T8).
2026-08-20 10:24:49 -04:00
Joseph Doherty e2497c908d test(cluster): guard that role reads live only in the authority (archreview theme 4) 2026-08-20 10:21:36 -04:00
Joseph Doherty 9bd3366b28 docs(plans): mark R3-01 T6 done 2026-08-20 10:21:01 -04:00
Joseph Doherty 306ea1aecf test(host): driver-only nodes carry no authorization middleware (03/S14 exemption pinned)
MeshGrpcDriverOnlyPipelineTests - the driver-role shape (no AddOtOpcUaAuth, no
UseAuthentication/UseAuthorization at all) behaves identically to the fused
shape's T4 positive/negative tests for NodeCommandService and
TelemetryStreamService: a correctly-keyed call succeeds, a no-key call gets
the interceptor's PermissionDenied + detail string.

Also asserts the STRUCTURAL reason it is unaffected - no
IAuthorizationPolicyProvider registered at all - so a future "register auth
everywhere for consistency" change fails this test before it ever reaches a
live site outage. Confirmed (by temporarily swapping in the pre-T2/T3 mapper)
that this class passes identically on both builds: driver-only nodes were
never exposed to 03/S14 in the first place, with or without
.AllowAnonymous().
2026-08-20 10:20:56 -04:00
Joseph Doherty e475f7b4ab Merge branch 'fix/r3-05-acl-hot-path-expiry' — R3-05: bounded ConfigDb API-key lookup (deadline + TTL) and clock-driven key-expiry revalidation of live monitored items (archreview 03/S18, 03/S19) 2026-08-20 10:19:13 -04:00
Joseph Doherty 62515f3279 fix(security): pin the cached-key expiry guard, meter cache_hit after it, and close two wiring gaps (review fixes) 2026-08-20 10:18:52 -04:00
Joseph Doherty 7d98ef9332 feat(runtime): audit refs still BadNoCommunication after a reconnect (dedup-trap tripwire) 2026-08-20 10:16:01 -04:00
Joseph Doherty 353ed6bb58 docs(plans): mark R3-01 T5 done 2026-08-20 10:15:03 -04:00
Joseph Doherty 8471db4b1d test(host): no-PSK mesh calls report the interceptor's verdict, not a cookie 401/redirect
Extends MeshGrpcAuthStackTests (T5): a no-bearer or wrong-bearer call to
DeploymentArtifactService.Fetch, TelemetryStreamService.Subscribe and
NodeCommandService.Execute fails with the INTERCEPTOR's detail string
("Config-serve authentication failed.", "Telemetry stream authentication
failed.", "Node command authentication failed."), asserted alongside the
status code rather than in place of it - the vacuity trap the plan calls out
(ConfigServeAuthInterceptor throws Unauthenticated, and Grpc.Net.Client can
map an HTTP 401 to the same status, so status alone would not discriminate a
broken build).

Verified red-then-green against the T1 (pre-AllowAnonymous) mapper: all 6 new
theory cases fail there with StatusCode.Internal ("HTTP/2 server reset the
stream, PROTOCOL_ERROR") instead of Unauthenticated/PermissionDenied - the
cookie challenge breaking the h2c stream before any gRPC trailers are
written, never a clean auth denial. That is the exact signature the live
gate (T10) should grep for; recorded in the class remarks.
2026-08-20 10:14:55 -04:00
Joseph Doherty 4380e2e78a fix(cluster): fail start when a late config layer changes Cluster:Roles (role overlays may not set it) 2026-08-20 10:11:25 -04:00
Joseph Doherty c1ad9e24d2 docs(archreview): R3-03 execution notes — final verification + deviation summary 2026-08-20 10:11:16 -04:00
Joseph Doherty 3314a1f98d feat(host): warn when Cluster:Roles and OTOPCUA_ROLES disagree; boot banner names the winning source 2026-08-20 10:08:03 -04:00
Joseph Doherty 815423bd88 docs(plans): restore literal UTF-8 em-dashes in R3-01 tasks.json (cosmetic) 2026-08-20 10:07:20 -04:00
Joseph Doherty 12c4028474 docs: restart re-provisioning + per-connection watchdog grace (R3-03) 2026-08-20 10:07:20 -04:00
Joseph Doherty 97f4ea0c9e fix(runtime): comms loss marks raw + UNS value nodes BadNoCommunication, value retained (archreview 03/S15) 2026-08-20 10:07:12 -04:00
Joseph Doherty af9d3a69ac docs(plans): mark R3-01 T4 done 2026-08-20 10:06:55 -04:00