docs(plan): r3 amendment — ScadaBridge register rows 38/39 join as Tasks 9-10
This commit is contained in:
@@ -413,6 +413,83 @@ because it exercises the real authoring path.
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Roslyn evaluator racing test — remove the timing assumption
|
||||
|
||||
**Classification:** standard
|
||||
**Estimated implement time:** ~5 min
|
||||
**Parallelizable with:** Task 10 (disjoint files); independent of the rig tasks
|
||||
|
||||
**Origin:** ScadaBridge deferred-work register row 38, relayed cross-session 2026-08-18
|
||||
(coordinated by the ScadaBridge session on Joe's behalf).
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/RoslynVirtualTagEvaluatorTests.cs`
|
||||
(`Evaluate_racing_ClearCompiledScripts_never_fails_with_disposed`, ~:391)
|
||||
|
||||
**Spec.** The test is load-fragile: it fails under full-solution parallel test load but
|
||||
passes 3/3 in isolation. Its design assumes at most ONE `ClearCompiledScripts` lands
|
||||
inside a single evaluation (so the product's one-shot retry suffices); under contention
|
||||
two can land and the retry legitimately exhausts. Test-design issue, NOT a product bug.
|
||||
Fix by removing the timing assumption while keeping the original intent (no
|
||||
disposed-script failure ever ESCAPES the retry): either bound the clear cadence relative
|
||||
to observed evaluation latency, or assert on retry-exhaustion behaviour instead of zero
|
||||
failures — implementer picks whichever preserves intent with less machinery, documents
|
||||
the choice in the test doc-comment, and demonstrates the test survives a
|
||||
full-parallel-load run (run the containing suite alongside another heavy suite, or
|
||||
repeat-run the suite ≥5×; report what was actually run).
|
||||
|
||||
---
|
||||
|
||||
### Task 10: LocalDb conditional registration + baseline + deregister self-heal
|
||||
|
||||
**Classification:** high-risk
|
||||
**Estimated implement time:** ~8 min
|
||||
**Parallelizable with:** Task 9 (disjoint files)
|
||||
|
||||
**Origin:** ScadaBridge deferred-work register row 39, relayed cross-session 2026-08-18.
|
||||
ScadaBridge closed the same class with conditional registration + boot-time self-heal
|
||||
(`Host/SiteLocalDbSetup.cs`, arch-review WP1.3/WP3.3); port that pattern here.
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbSetup.cs` (`OnReady`,
|
||||
registration at :63-66)
|
||||
- Read for the predicate: `src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbRegistration.cs`
|
||||
(`LocalDb:SyncListenPort`, `LocalDb:Replication:*` keys)
|
||||
- Modify: `tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/LocalDbSetupTests.cs`
|
||||
(the registered-table-set + ordering pins — update DELIBERATELY, say so in the diff)
|
||||
- Modify: `CLAUDE.md` LocalDb section — the two bullets recording unconditional
|
||||
registration as deliberate ("DeregisterReplicated exists and this repo deliberately
|
||||
does not call it" / "Consequently, baselineExistingRows is not used either") must be
|
||||
rewritten to record the new conditional shape and why it changed.
|
||||
|
||||
**Spec.**
|
||||
- "Replication configured" = `PeerAddress` OR `ApiKey` present (OR, because only the
|
||||
dialling half sets PeerAddress while the passive half carries just the key + listen
|
||||
port — verify the exact key names against `LocalDbRegistration`).
|
||||
- Configured ⇒ `RegisterReplicated(table, baselineExistingRows: true)` per table, in the
|
||||
existing DDL→register order (the ordering is load-bearing and pinned) — seeds
|
||||
row-versions at the LWW floor and flags snapshot resync, so late-enabled replication
|
||||
on an established node converges on EXISTING rows, not just subsequent writes.
|
||||
- NOT configured ⇒ `DeregisterReplicated` on every table at boot — idempotent self-heal:
|
||||
drops stale triggers, prunes oplog/row-version rows, logs once at Information when it
|
||||
actually cleaned something (and stays silent when there was nothing to clean).
|
||||
- Registration stays per-table in a loop (0.3.0 made the snapshot flag per-table; the
|
||||
loop preserves that granularity if the pin ever moves).
|
||||
- **Pin note:** this repo is on `ZB.MOM.WW.LocalDb 0.2.1`, which has both
|
||||
`baselineExistingRows` and `DeregisterReplicated`. Do NOT bump the pin unless a needed
|
||||
API is genuinely absent at 0.2.1 — if it is, stop and report before bumping.
|
||||
- **Traps (both hit by ScadaBridge):** the registered-table set is pinned by
|
||||
`LocalDbSetupTests` — update the pins deliberately with the behaviour change named in
|
||||
the test doc-comment; and deregistration must be pair-symmetric — the sync handshake
|
||||
compares registered-table digests fail-closed, so flipping registration is a
|
||||
both-nodes-together change. Record the pair-symmetry consequence in CLAUDE.md and in
|
||||
the rollout note (docker-dev site-a has replication ON and was enabled from fresh, so
|
||||
current impact is low; the change is correctness-before-someone-enables-late).
|
||||
- Tests: pin the new predicate both ways (configured ⇒ registered with baseline;
|
||||
unconfigured ⇒ deregistered/self-healed) and keep the DDL→register ordering pin green.
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Close-out — sweep + Outcomes + register annotations
|
||||
|
||||
**Classification:** close-out (blockedBy: all)
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 1: ACL InvalidateIfStale — monotonic-max CAS loop", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 2: S7 read-pass health — worst-state-wins aggregation", "status": "pending"},
|
||||
{"id": 3, "subject": "Task 3: Fix stale OTOPCUA_CONFIG_MODE docs", "status": "pending"},
|
||||
{"id": 3, "subject": "Task 3: Fix stale OTOPCUA_CONFIG_MODE docs", "status": "completed", "note": "e9ba1741; docs/Configuration.md:253 + CLAUDE.md:554 rewritten (compose facts verified first); historical plan docs deliberately untouched; trivial => no review"},
|
||||
{"id": 4, "subject": "Task 4: Live gate — first-POST-404 catch + no-body POST binding", "status": "pending", "blockedBy": [1, 2, 3]},
|
||||
{"id": 5, "subject": "Task 5: Live gate — FetchAndCache legs", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "Task 6: Live gate — alarm-bearing surgical ratchet", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 7: Live gate — ACL-enabled enforcement on site-a", "status": "pending", "blockedBy": [6]},
|
||||
{"id": 8, "subject": "Task 8: Close-out — sweep + Outcomes + register annotations", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7]}
|
||||
{"id": 8, "subject": "Task 8: Close-out — sweep + Outcomes + register annotations", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 9, 10]},
|
||||
{"id": 9, "subject": "Task 9: Roslyn evaluator racing test — remove timing assumption (ScadaBridge row 38)", "status": "pending"},
|
||||
{"id": 10, "subject": "Task 10: LocalDb conditional registration + baseline + deregister self-heal (ScadaBridge row 39)", "status": "pending"}
|
||||
],
|
||||
"lastUpdated": "2026-08-17T19:05:00Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user