docs: close residual #3 (R2 merged); register row 47 (CTS-dispose bug, fixed in-flight); correct stale MaxBatchSize story in Component-StoreAndForward (R1 finding)
This commit is contained in:
@@ -40,6 +40,7 @@ All 7 fix-now items landed via PLAN-04/05/06/07/08 (verified in review 08 round
|
||||
| 44 | **`BundleUnlockRateLimiter` is dead code — the documented per-IP unlock throttle is not enforced** *(Medium — security)* | Found by R6 | Registered, injected into `BundleImporter`, unit-tested, documented on `TransportOptions.MaxUnlockAttemptsPerIpPerHour = 10`, pinned in `appsettings.Central.json` — but `_unlockRateLimiter` is never invoked anywhere in `src/`. A brute-force passphrase attacker is bounded only by the per-bundle 3-strike lockout, not the advertised 10/hour/IP cap. Fix: call it on the unlock path (or delete it and the config surface honestly). | Fix-now candidate — next Transport/security session |
|
||||
| 45 | **Notification Outbox dispatcher shares one `DbContext` across up to 4 parallel deliveries** *(Medium — correctness under load)* | Found by R6; observed continuously on rig central-b: `InvalidOperationException: A second operation was started on this context instance` on dispatch | `NotificationOutboxActor.ResolveAdapters()` caches adapter instances in an actor-lifetime `IServiceScope` while the Email/Sms adapters are `AddScoped` and hold a scoped `INotificationRepository` (`ScadaBridgeDbContext`); `MaxParallelDeliveries` (4) then shares one context across concurrent deliveries. The per-delivery scope added in `DeliverGatedAsync` for exactly this hazard covers only `INotificationOutboxRepository` — the adapter escapes it. Effect today: failed attempts + retries + error noise; sustained contention could burn a row to `Parked`. Fix: resolve adapters inside the per-delivery scope. | Fix-now candidate — next Notification Outbox session, or first `Parked` row attributed to it |
|
||||
| 46 | **CLI `audit-config query` returns `{}` for every filter combination** *(Low)* | Found by R6 against the rig; underlying `AuditLogEntries` rows verified present | Possibly a CLI output-shape/serialization issue; not investigated (out of R6 scope). | Next CLI session |
|
||||
| 47 | ~~**`SiteStreamGrpcServer` duplicate-stream replacement disposed another handler's `CancellationTokenSource`**~~ **RESOLVED 2026-08-15 (found and fixed in-flight by R2, `e6842c10`).** `SubscribeInstance`'s duplicate-prevention path called `Cancel()` AND `Dispose()` on the replaced stream's CTS, which belongs to that still-running handler's `using var` and is still read — escaping as an unhandled `ObjectDisposedException` under scheduling pressure (confirmed pre-existing on `2b74851f`; load-only, passed 10/10 isolated). Fixed to cancel-only (the owner's `using` disposes exactly once), `Cancel` wrapped for the converse race, mirroring `CancelAllStreams()`. Deterministic regression test via a gate inside the setup window, fail-before/pass-after verified by reinstating the `Dispose()` as a negative control. Recorded here (though closed) because it is a product-bug instance of the disposal-race class, useful precedent for future stream-lifecycle work. | Closed 2026-08-15. |
|
||||
|
||||
## Resolved (verified against the code 2026-07-10)
|
||||
Rows removed from the Deferred table above once confirmed shipped. Kept here for traceability.
|
||||
|
||||
@@ -113,9 +113,19 @@ Deliberately not fixed in this program — each has a stated reason, not an over
|
||||
Library suite 199/199 incl. old-peer interop; ScadaBridge Host 490/490 on the bump. Adding an
|
||||
eleventh table to `ReplicatedTables` on a replicating site now snapshots ONE table instead of
|
||||
re-streaming all, both directions.
|
||||
3. **Event batching per proto message.** Individual `AttributeValueChanged`/`AlarmStateChanged`
|
||||
events still ride one gRPC message each; batching them is a new wire shape (proto + both
|
||||
client/server), deferred rather than folded into this program's additive-only changes.
|
||||
3. ~~**Event batching per proto message.**~~ **RESOLVED 2026-08-15 (`9b5cb3dd`+`e6842c10`,
|
||||
R2).** Additive wire shape, no new RPC: `batching_supported` request flags +
|
||||
`SiteStreamEvent.batch` (oneof field 4) carrying `SiteStreamEventBatch`. Negotiation is the
|
||||
proto3 default itself and is LOAD-BEARING — an un-negotiated batch frame would parse as
|
||||
`EventOneofCase.None` on an old central and vanish silently, so an old central (which cannot
|
||||
set the flag) never receives one; an old site ignores the flag and keeps per-event frames.
|
||||
Per-subscriber `SiteStreamEventBatcher` downstream of the relay's bounded channel (framing
|
||||
only — never delays a lone event; lingers only once a backlog is proven); defaults 100
|
||||
events / 25 ms, options-validated strictly < 250 ms to pin the load-test latency budget.
|
||||
Measured worst case P99 25.4 ms; 600 queued events → 6 frames. Skew proven both directions
|
||||
over real wire bytes. Histogram `scadabridge.site.stream.batch_size` (allowlist-verified).
|
||||
Does NOT change the burst ceiling of register row 31 (documented). In-flight it also found
|
||||
and fixed a pre-existing `SiteStreamGrpcServer` bug (register row 47).
|
||||
4. ~~**Deployments page server-side paging + status counts.**~~ **RESOLVED 2026-08-15
|
||||
(`35ce1413`, R3).** The Deployments page is database-paged: `QueryDeploymentListPageAsync`
|
||||
returns one instance-joined page + the filtered total, `GetDeploymentStatusCountsAsync`
|
||||
|
||||
Reference in New Issue
Block a user