docs(plan): perf follow-ups close-out — outcomes, live-gate evidence, register annotations

This commit is contained in:
Joseph Doherty
2026-08-17 04:42:51 -04:00
parent 7c5a843ace
commit 07fd17a34e
4 changed files with 355 additions and 35 deletions
+4 -3
View File
@@ -763,9 +763,10 @@ Two things to know before relying on any of this:
`TriePermissionEvaluator`'s, so **any divergence between the two clocks denies every request** with a
verdict indistinguishable from "no grant matched". Found by a test clock two hours ahead.
- **Stage 6's hot seams (read/browse/subscribe) go through a per-`(session, node, operation, realm)`
decision cache**, cleared wholesale when the generation advances. It is **sharded per session**, so a
single client crawling a large tree past the per-session ceiling evicts only its own shard and not
every other session's decisions. ⚠️ It caches the **grant walk only** — key revocation and expiry are
decision cache**, cleared wholesale when the generation advances. It is **sharded per API key**
(`ApiKeyId ?? Principal` — despite the type name, sessions sharing one key share one shard and one
budget), so a client crawling a large tree past the per-key ceiling evicts only that key's shard and
not every other key's decisions. ⚠️ It caches the **grant walk only** — key revocation and expiry are
re-checked **before** the cache lookup on every call, because expiry moves with the clock rather than
with a deployment.
- **Browse needs ancestor visibility, and it must NOT be a synthesized grant on the ancestors.** A
+53 -16
View File
@@ -1340,6 +1340,12 @@ whose message mentions it.
Open items the work surfaced but deliberately did not close. None blocks the branch.
> **Annotated 2026-08-17 by the `perf/followups` branch** (plan:
> [`2026-08-17-perf-followups.md`](2026-08-17-perf-followups.md)). Items it closed carry a
> ` — closed by <sha>, 2026-08-17` suffix; items with no suffix remain open. Annotations are
> **append-only** — the original text of each item is untouched, including where the follow-up
> work reached a different conclusion than the item predicted.
**Cross-repo**
- **HistorianGateway needs a multi-tag batch `WriteLiveValues` RPC.** Client `0.3.0` exposes
@@ -1351,22 +1357,31 @@ Open items the work surfaced but deliberately did not close. None blocks the bra
- **S7: multi-variable PDUs for span leftovers.** The Task 13 planner groups contiguous
reads into one PDU per span; tags that do not fall in a span are still one PDU each. A
multi-var PDU would absorb them.
multi-var PDU would absorb them. — closed by `20022083`, 2026-08-17 (per-chunk fallback:
S7.Net throws for the whole multi-var request, so a bad item fails its chunk, not the pass;
Timer/Counter areas are excluded on an int-vs-ushort box-parity difference).
- ⚠️ **S7 span planner has no live gate.** The `16` (max gap) / `220` (max span bytes)
defaults were chosen analytically and pinned only by unit tests. Run the planner against
the `s7_1500` fixture (`lmxopcua-fix up s7 s7_1500`, `S7_SIM_ENDPOINT=10.100.0.35:1102`)
before trusting them on real hardware.
before trusting them on real hardware. — closed by `57c66416`, 2026-08-17 (**LIVE GATE
PASSED** vs `10.100.0.35:1102`; 3 wire requests for 10 tags with spans on vs 10 with spans
off).
- **`SpanReadMaxSpanBytes` has no validation guard.** A value above the negotiated PDU size
is accepted at config time and fails at read time.
is accepted at config time and fails at read time. — closed by `acb37724`, 2026-08-17.
- **MTConnect `Build` could stream through `XmlReader`.** Task 27 removed the per-chunk
string copies; the document build still materialises.
string copies; the document build still materialises. — closed by `cda82c07`, 2026-08-17
(566/566 parity, ~19% allocation win; one disclosed error-ordering change on a
double-broken document).
- **No driver-level meter for the PollGroupEngine overrun counter.** The counter exists and
is observable in-process but is not exported per driver.
is observable in-process but is not exported per driver. — closed by `e22ecea0`, 2026-08-17
(tag keys moved to snake_case consts in `97902d68` after review).
- **`NoteOverrun` conflates a slow success with a slow failure** — a poll that overran
because the device was slow and one that overran because it timed out increment the same
counter. Worth a doc caveat rather than a code change.
counter. Worth a doc caveat rather than a code change. — closed by `e22ecea0`, 2026-08-17
(doc caveat, as proposed).
- **`backoffCap` wants a constructor doc note** so a future caller does not have to read the
loop to learn its units and its interaction with the poll interval.
loop to learn its units and its interaction with the poll interval. — closed by `e22ecea0`,
2026-08-17.
**Runtime / deploy path**
@@ -1377,11 +1392,14 @@ Open items the work surfaced but deliberately did not close. None blocks the bra
review, Important). The read now runs off the mailbox so it cannot wedge the actor, but a
hung SQL read still pins a thread-pool thread indefinitely unless the `ConfigDb`
connection string's command timeout bounds it. **Confirm that it does** — the code relies
on it and does not assert it.
on it and does not assert it. — closed by `dba5f806`, 2026-08-17 (explicit 30 s
`SetCommandTimeout` behind an `IsRelational()` guard; EF 10.0.7 verified empirically to
reset it on return to the pool, so there is no pool pollution).
- **The historian drain's `catch` swallows a shutdown `OperationCanceledException` as a
per-tag failure** when other tags in the same pass succeeded, spuriously incrementing
`_tagWriteFailures` (Task 3 review; pre-existing, inherited). Rethrowing a genuine
`ct` cancellation is the fix.
`ct` cancellation is the fix. — closed by `7870c91f`, 2026-08-17 (the red run proved the
bug also discarded healthy tags' samples at shutdown, not just the counter).
- **Host-side mux interest gating was skipped** — the batch envelope (Task 2) made the
saving it would have bought immaterial. Recorded so it is not re-proposed as an oversight.
@@ -1389,30 +1407,42 @@ Open items the work surfaced but deliberately did not close. None blocks the bra
- **Task 6: `_globalCount` can drift under concurrent shard eviction**, and there is no
concurrency stress test. Drift is bounded and only affects when trimming triggers, not
correctness of any decision.
correctness of any decision. — closed by `d8849345`, 2026-08-17 (the planned counter swap
alone was insufficient: a seal sentinel plus a `TryBookInsert` compare-exchange were both
needed, each proven necessary by a 6/6-red falsification run).
- **Task 9: the empty-identifier pattern should be tightened** — the dedup key tolerates an
empty node identifier rather than rejecting it.
empty node identifier rather than rejecting it. — closed by `c4d8e4a2`, 2026-08-17
(pattern-form guard; the bypass is pinned by a clock-call count).
- **Task 17: the `GroupBy(_ => 1)` aggregate is unverified against real SQL Server.** It is
covered only by EF InMemory, which does not exercise the provider's translation, so the
single-round-trip claim is untested where it matters.
single-round-trip claim is untested where it matters. — closed by `c149410b`, 2026-08-17
(aggregate confirmed live against SQL Server; the suite skips cleanly offline via an
xunit-v2 `SqlServerFactAttribute`).
- **Task 21: the `CHARINDEX`/collation widening needs a SQL-Server-backed test**, and the
worst-case **250 sequential round-trips** (a rename touching the chunking limit) should
carry a one-line comment at the call site.
carry a one-line comment at the call site. — closed by `c149410b`, 2026-08-17 (collation
widening confirmed live; round-trips comment added).
**Alarms**
- **Task 15: `TrackBackgroundTask` has a registration-ordering gap** (pre-existing) — a task
can complete before it is registered. Also `_disposed` is non-volatile.
can complete before it is registered. Also `_disposed` is non-volatile. — closed by
`5ae43023`, 2026-08-17 (a TCS-placeholder register-first factory closes the ordering gap;
`_disposed` made volatile).
**Test-suite reliability** (affects how reliably a regression is *caught*, not shipped behaviour)
- **Task 1's second-reader durability test is fragile on macOS** — it depends on filesystem
flush visibility to a second reader, which the platform does not guarantee on the same
timescale as Windows/Linux.
timescale as Windows/Linux. — closed by `6b46c485`, 2026-08-17 (a robust bounded wait
replaced the fixed sleep; the anticipated macOS skip turned out to be unnecessary —
verified empirically, the reader does become visible).
- **The #491 falsification control is nondeterministic under load.** It relies on a
`ThreadStatic` actor cell, so under a loaded sweep the intended-red mutation can
occasionally pass. The test is still worth keeping; do not read a single green run of it
as proof the regression is caught.
as proof the regression is caught. — closed by `6b46c485`, 2026-08-17 (documented in the
test file as an honest nondeterminism note; the nondeterminism itself is inherent and
remains).
**Accepted-not-fixed at the final integration review (2026-08-15)** — carried knowingly, none
blocking: the poll **pacing** behaviour change ships with no production consumer of the overrun
@@ -1423,3 +1453,10 @@ branch (docker-dev recipe:
`ScriptTagCatalogSnapshotCache` expires on **wall-clock TTL**; the S7 span reader **stamps a partial
span** when the PDU comes back short; MTConnect's `IsFramingOnly` fast path is **ASCII-only**; and a
minor Razor exposure where `StateHasChanged` runs inside a `finally`.
**Annotation, 2026-08-17 (append-only).** Two of the accepted-not-fixed items above have since been
retired by `perf/followups`: the overrun counter **now has a production consumer** (the per-driver
meter, `e22ecea0` + `97902d68`), and this branch's code state **has been live-gated** — S7 span reads
against the `s7_1500` fixture (`57c66416`) and a full docker-dev regression on the merged remainder
(2026-08-17, no commit). The `_globalCount` drift item is likewise closed (`d8849345`). The rest of
the paragraph stands as written.
+165
View File
@@ -613,3 +613,168 @@ are glob noise, not failures). (3) Outcomes + register annotations + ledger. (4)
update is the orchestrator's job, not this task's.
**Commit:** `docs(plan): perf follow-ups close-out — outcomes, register annotations, sweep record`
---
## Outcomes
*Written at close-out, 2026-08-17, on branch `perf/followups`. All 15 tasks complete; both
release-condition live gates PASSED.*
### Sweep record
`dotnet build ZB.MOM.WW.OtOpcUa.slnx` → **0 errors**, 896 warnings (all pre-existing:
`xUnit1051` cancellation-token advice and the repo's own `OTOPCUA0001` CapabilityInvoker
analyzer firing on test call sites).
Full unit sweep — every `*.Tests` project in the solution, run per-project with `--no-build`
against the tree at the close-out commit. `*.IntegrationTests` are build-only by convention
and were not run in the sweep (the S7 suite ran separately as Task 13's live gate; the two
empty `tests/Drivers/…Driver.Historian.Wonderware*.Tests` directories carry no csproj and are
glob noise).
**43 / 43 test projects green (exit code 0 on every one).**
| | |
|---|---|
| Passed | **10 358** |
| Failed | **0** |
| Skipped | 19 |
| **Total** | **10 377** |
No test failed, so no isolation re-run was needed. All 19 skips are **pre-existing, env-gated**
suites, spread across five projects — `Core.Tests` (1), `Driver.Galaxy.Tests` (8),
`Driver.Historian.Gateway.Tests` (6, the `LiveIntegration` category that needs the
`wonder-sql-vd03` VPN), `OpcUaServer.Tests` (1), `Runtime.Tests` (3). **None of the skips are
the new SQL-Server-backed tests**: `AdminUI.Tests` ran **1537 / 0 skipped** and
`Configuration.Tests` **190 / 0 skipped**, which is the evidence that Task 7's pins actually
executed against a real SQL Server rather than skipping past themselves.
The DB-backed suites (`Configuration.Tests`, `AdminUI.Tests` including Task 7's new
SQL-Server-backed pins) ran **against a reachable** `10.100.0.35,14330` — they were executed,
not skipped. None of the known load-flake candidates flaked
(`DriverInstanceActorCommsLossWatchdogTests`, `RawTreeServiceRenameWarningTests`,
`RouteNativeAlarmAck_on_non_primary_is_dropped`, plus this branch's new
`SessionAclDecisionCache` concurrency stress test and the `ContinuousHistorizationRecorder`
timing tests).
### Per-task outcomes
| # | Task | Commit(s) | Outcome + deviation |
|---|---|---|---|
| 1 | Per-driver poll-overrun meter + `PollGroupEngine` doc notes | `e22ecea0`, `97902d68` | Done. Review deviation: the tag keys were literals; moved to snake_case consts (`driver_type` / `driver_instance`) so they match the rest of the Meter and a dashboard's group-by cannot split across two spellings. |
| 2 | `SpanReadMaxSpanBytes` validation guard | `acb37724` | Done, as planned — validated at parse time, with the over-PDU degradation documented. |
| 3 | Historian drain rethrows a shutdown `OperationCanceledException` | `7870c91f` | Done. Deviation in *severity*, not shape: the red run showed the bug did not merely inflate `_tagWriteFailures` — a shutdown mid-drain **discarded healthy tags' samples**. A pass abort, not a per-tag failure. |
| 4 | Bound the Direct-mode `LoadArtifactBlob` read | `dba5f806` | Done, with one addition the plan did not anticipate: an `IsRelational()` guard, because the extension throws on the InMemory provider the actor tests use. EF 10.0.7 verified **empirically** to reset the timeout on return to the pool, so the local `SetCommandTimeout` cannot pollute the pooled factory. ⚠️ The relational branch is consequently untested in-suite. |
| 5 | ACL `_globalCount` drift + concurrency stress test | `d8849345` | Done — **beyond the planned scope, and provably so.** The planned counter swap alone was insufficient; a seal sentinel *and* a `TryBookInsert` compare-exchange were both required. Falsification: removing either went 6/6 red. |
| 6 | Revalidation memo rejects empty node identifiers | `c4d8e4a2` | Done as a pattern-form guard mirroring the authorizer's; the bypass is pinned by a clock-call count rather than by observing the memo directly. |
| 7 | SQL-Server-backed pins for the drift watermark + rename collation | `c149410b` | Done. Deviation: `AdminUI.Tests` is pinned to **xunit v2** by `Akka.TestKit.Xunit2`, so `Assert.Skip` is unavailable — an overriding `SqlServerFactAttribute` gives a genuine skip instead. Both the `GroupBy` watermark aggregate and the `CHARINDEX` collation widening confirmed **live** against SQL Server. |
| 8 | `TrackBackgroundTask` registration ordering + volatile `_disposed` | `5ae43023` | Done via a TCS-placeholder **register-first** factory (registration precedes the work starting, closing the gap at its root rather than narrowing it); `_disposed` made volatile. |
| 9 | Durability-test robustness + honest #491 nondeterminism notes | `6b46c485` | Done. Deviation: the planned macOS skip proved **unnecessary** — verified empirically that a bounded wait is sufficient, so the test runs on macOS rather than skipping. The #491 falsification control's nondeterminism is inherent and is now documented, not fixed. |
| 10 | MTConnect sample-chunk parsing through `XmlReader` | `cda82c07` | Done. 566/566 parity against the pre-change parser, ~19% allocation win. One **disclosed** behaviour change: on a doubly-broken document the *ordering* of the reported errors differs. |
| 11 | Expose `ReadMultipleVarsAsync` through the `IS7Plc` seam | `6ee553cf` | Done, as planned. |
| 12 | S7 span leftovers batch through multi-var read PDUs | `20022083` | Done, with two forced deviations: S7.Net throws for the **whole** multi-var request rather than per item, so a bad item is handled by a **per-chunk fallback**; and Timer/Counter areas are **excluded** because S7.Net boxes them as `int` where the single-read path yields `ushort`, and box parity is load-bearing downstream. |
| 13 | S7 live gate (span reads + multi-var leftovers) | `57c66416` | **LIVE GATE PASSED** vs `10.100.0.35:1102`. Evidence below. |
| 14 | docker-dev live gate (regression of the merged remainder) | *(no commit — evidence below)* | **LIVE GATE PASSED.** Evidence below. |
| 15 | Close-out — full sweep + Outcomes | `7c5a843a`, *this commit* | Done. `7c5a843a` carries the review round's comment-only fixes; this commit carries the Outcomes, the live-gate evidence, the register annotations, and the ledger. |
### Live gate — S7 span reads (Task 13)
**2026-08-17, against the `s7_1500` fixture at `10.100.0.35:1102`. PASSED.**
- **Results:** 3/3 span-read tests green; 7 passed / 1 skipped across the full
`Device=S7_1500` suite. The single skip is the env-gated SYN-blackhole test, which needs a
recipe the fixture does not provide — not a silent skip of the gated behaviour.
- **Wire shape (the actual claim under test):** with spans **on**, 10 tags produced
**3 requests** — 2 block reads + 1 multi-var read carrying 3 items. With spans **off**, the
same 10 tags produced **10 requests**. Multi-variable and cross-area batching both confirmed
on the wire, and a falsification bit was flipped to prove the counter was measuring the real
traffic.
- **Reconnect suite** ran **live** (not simulated) via `S7_RECONNECT_BOUNCE_CMD` and passed.
- **Not live-pinned:** the Timer/Counter exclusion — the fixture has no TM/CT area, so the
box-parity reasoning behind excluding them rests on unit tests only.
- **Ops notes for whoever re-runs this** (both cost time on this run):
- The fixture **profile is baked into the image**, so `--build` is mandatory after editing a
profile. Confirm the seed actually took by looking for
`seeded DB1 … seeds=<n>` in `docker logs`; a stale image seeds the old profile silently.
- `/opt/otopcua-s7` on the docker host is **root-owned**, so a plain `rsync` fails with
status 23 **while looking successful** in its summary output. Use
`--rsync-path="sudo rsync"`.
### Live gate — docker-dev regression (Task 14)
**2026-08-17, local OrbStack rig, branch at `6b46c485`, ~14.5 min wall. PASSED.**
- **Boot:** clean-slate build + boot; 8 containers healthy, `RestartCount=0` across the board.
- **Deploy:** `c50678b2` sealed in **306 ms** with **6/6 Applied**.
- **Data plane:** browse / read / write end-to-end against the pymodbus fixture; a **180 s**
subscription streamed with **0 Bad** values.
- **Surgical apply (the headline regression target):** a **real** UNS-reference surgical apply
— deploy `3df4b321`, logging
`applied plan kind=PureAdd added=2 changed=1 surgicalRawTags=1 rebuild=False`. The open
subscription streamed **Good across the apply**, and the newly-added UNS mirror served live
immediately. This is the behaviour the 2026-08-15 surgical-deploys work exists to protect,
exercised as a genuine surgical path rather than a rebuild in disguise.
- **Deploy gate:** correctly rejected a malformed first attempt with **422**
(`UnsSegmentInvalid`, `EquipmentIdNotDerived`) — the gate is live, not bypassed.
- **AdminUI:** `/fleet`, `/alerts`, `/hosts`, `/uns`, `/raw`, `/deployments` all 200 with live
data.
- **Logs:** 0 ERR/FTL, excluding the by-design standby 503s.
- **Teardown:** clean.
- **Anomalies (non-blocking, all disclosed):**
- A transient **404 on the first `POST /api/deployments`** ~40 s after boot; the retry
succeeded. Scripted deploys should **poll** rather than fire once (see the follow-up
register below).
- Boot-time telemetry-dial **reconnect warnings**, settling within one cycle.
- `ScriptLogEntry` **dead-letters on site nodes**, one per deploy — **pre-existing**, not
introduced by this branch.
- **NOT exercised (say so plainly, do not read the pass as wider than it is):** historian
end-to-end (needs the VPN), the `FetchAndCache` flip, the ACL-**enabled** enforcement path,
the alarm-bearing surgical **ratchet** to rebuild, load-scale throughput, and real-browser
Blazor circuits.
### Follow-up register (opened by this round)
Open items this branch surfaced but deliberately did not close. **None blocks the branch.**
**ACL**
- ⚠️ **Pre-existing generation-rollover window in `SessionAclDecisionCache.InvalidateIfStale`.**
While the CAS winner is running `ClearAll()`, a racing caller can still serve a
stale-generation verdict out of a shard the winner has not purged yet. The window is
microseconds and **key revocation/expiry are unaffected** (both are re-checked *above* the
cache on every call), so this is a stale *grant walk*, not a stale key. Fix shape if it ever
matters: stamp the generation on `Shard` itself, or make the racer wait on the winner.
- **A fresh `Shard` could take an initial-capacity hint** for a session known to be crawling,
to avoid the dictionary's growth rehashes on exactly the workload the per-key ceiling exists
for.
**S7**
- **No cancellation-branch test on any read path.** Pre-existing, but this branch added a third
path (multi-var), so the gap now spans three.
- **No integration case for one bad decode inside an otherwise-good chunk** — the per-chunk
fallback is unit-tested only.
- **The Timer/Counter exclusion needs a live pin**, which needs a fixture with a TM/CT area.
**Historian**
- **Task 3's `Task.Delay(500)` is still a hope-wait** and could become a bounded poll, the same
treatment Task 9 gave the durability test.
**AdminUI tests**
- **A mid-run SQL connectivity blip surfaces as `SqlException` failures, not skips** — the
reachability probe is one-shot, taken before the run.
- **Orphaned test databases have no reaper.** A killed test process leaves its uniquely-named
database behind on `10.100.0.35,14330`.
**MTConnect**
- **`AsStream` wants a buffer-ownership comment** so a future caller does not have to infer the
lifetime from the call site.
**docker-dev**
- **The first `POST /api/deployments` after boot can 404.** Worth a readiness gate on the
deployments endpoint, so scripted deploys do not have to encode a retry.
@@ -1,21 +1,138 @@
{
"planPath": "docs/plans/2026-08-17-perf-followups.md",
"tasks": [
{"id": 1, "subject": "Task 1: Per-driver poll-overrun meter + PollGroupEngine doc notes", "status": "pending"},
{"id": 2, "subject": "Task 2: S7 span-option validation guard", "status": "pending"},
{"id": 3, "subject": "Task 3: Historian drain — shutdown cancellation is not a per-tag failure", "status": "pending"},
{"id": 4, "subject": "Task 4: Direct-apply artifact read — explicit command timeout", "status": "pending"},
{"id": 5, "subject": "Task 5: SessionAclDecisionCache eviction race + stress test", "status": "pending"},
{"id": 6, "subject": "Task 6: Revalidate memo — reject empty node identifiers", "status": "pending"},
{"id": 7, "subject": "Task 7: SQL-Server-backed tests for drift probe + rename scan", "status": "pending"},
{"id": 8, "subject": "Task 8: ScriptedAlarmEngine TrackBackgroundTask ordering + volatile _disposed", "status": "pending"},
{"id": 9, "subject": "Task 9: Test-reliability hardening (durability second-reader, #491 controls)", "status": "pending", "blockedBy": [3]},
{"id": 10, "subject": "Task 10: MTConnect Build streams through XmlReader", "status": "pending"},
{"id": 11, "subject": "Task 11: IS7Plc multi-variable read seam", "status": "pending"},
{"id": 12, "subject": "Task 12: S7 multi-var reads for span leftovers", "status": "pending", "blockedBy": [1, 11]},
{"id": 13, "subject": "Task 13: S7 live gate — span planner + multi-var vs s7_1500 fixture", "status": "pending", "blockedBy": [2, 12]},
{"id": 14, "subject": "Task 14: docker-dev live gate (regression of the merged remainder)", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]},
{"id": 15, "subject": "Task 15: Close-out — full sweep + Outcomes", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]}
{
"id": 1,
"subject": "Task 1: Per-driver poll-overrun meter + PollGroupEngine doc notes",
"status": "completed",
"note": "e22ecea0 + fix 97902d68 (snake_case tag consts); spec ✅ + code APPROVED; close-out nit: stale doc comment on PollOverruns at OtOpcUaTelemetry.cs:50 still says driver.type"
},
{
"id": 2,
"subject": "Task 2: S7 span-option validation guard",
"status": "completed",
"note": "acb37724; code review APPROVED; deviations: new test file, 2 doc corrections (<= -1 wording)"
},
{
"id": 3,
"subject": "Task 3: Historian drain — shutdown cancellation is not a per-tag failure",
"status": "completed",
"note": "7870c91f; spec ✅ + code APPROVED (Minor: Task.Delay(500) hope-wait at test :590); red run proved healthy-tag sample discard at shutdown; falsification via when-guard weakening"
},
{
"id": 4,
"subject": "Task 4: Direct-apply artifact read — explicit command timeout",
"status": "completed",
"note": "dba5f806; spec ✅ + code APPROVED; IsRelational guard (InMemory tests); EF 10.0.7 pool-reset verified empirically; minor for close-out: comment could state the relational branch is untested in-suite"
},
{
"id": 5,
"subject": "Task 5: SessionAclDecisionCache eviction race + stress test",
"status": "completed",
"note": "d8849345; spec ✅ (interleavings traced) + code APPROVED; falsification 6/6 red reverted. FOLLOW-UP for register (pre-existing, Important, non-blocking): InvalidateIfStale generation-rollover window — a racer can serve a stale-generation verdict from a not-yet-purged shard during the winner's ClearAll (microseconds; revocation/expiry unaffected). Fix shape: generation stamp on Shard or wait-on-winner. Minors: fresh-Shard capacity hint, un-elected global ClearAll comment, CAS backoff, stress-test CI wall-time, 'every booked entry' wording"
},
{
"id": 6,
"subject": "Task 6: Revalidate memo — reject empty node identifiers",
"status": "completed",
"note": "c4d8e4a2; code review APPROVED (pattern-form deviation positive); bypass-count test shape (clock.Calls==2), clock-read-above-guard verified"
},
{
"id": 7,
"subject": "Task 7: SQL-Server-backed tests for drift probe + rename scan",
"status": "completed",
"note": "c149410b; spec ✅ + code APPROVED (5 Minors: mid-run blip not skipped, kill-orphaned DBs, duplicated 200 const in test, ~3s discovery probe offline, TryDropDatabase catches DbException only); xunit-v2 SqlServerFactAttribute skip; 10/10 ran live vs 10.100.0.35"
},
{
"id": 8,
"subject": "Task 8: ScriptedAlarmEngine TrackBackgroundTask ordering + volatile _disposed",
"status": "completed",
"note": "5ae43023; spec ✅ + code APPROVED (2 Minors: null-coalesce comment, remove-before-complete ordering comment); TCS placeholder register-first, volatile _disposed, handover Task.Run preserved"
},
{
"id": 9,
"subject": "Task 9: Test-reliability hardening (durability second-reader, #491 controls)",
"status": "completed",
"blockedBy": [
3
],
"note": "6b46c485; code review APPROVED; robust-wait path (empirically verified fresh-reader re-open observes later commits; xunit v3 skip was available but unneeded); both #491 tests carry load-nondeterminism warnings"
},
{
"id": 10,
"subject": "Task 10: MTConnect Build streams through XmlReader",
"status": "completed",
"note": "cda82c07; spec ✅ + code APPROVED (2 Minors: pre-existing error-phrase asymmetry, AsStream ownership comment); disclosed diff: double-broken doc reports observation error first; ~19% alloc win, honest"
},
{
"id": 11,
"subject": "Task 11: IS7Plc multi-variable read seam",
"status": "completed",
"note": "6ee553cf; code review APPROVED; no shared delegate-hook fake exists — Task 12 gives ImageS7Plc a real multi-var impl"
},
{
"id": 12,
"subject": "Task 12: S7 multi-var reads for span leftovers",
"status": "completed",
"blockedBy": [
1,
11
],
"note": "20022083; spec ✅ + code APPROVED; Timer/Counter excluded (int vs ushort box parity); S7.Net throws wholesale → per-chunk fallback; 2 Minor coverage gaps for register: no cancellation-branch test on any read path (pre-existing), no one-bad-decode-in-chunk integration case"
},
{
"id": 13,
"subject": "Task 13: S7 live gate — span planner + multi-var vs s7_1500 fixture",
"status": "completed",
"blockedBy": [
2,
12
],
"note": "57c66416; GATE PASSED live vs 10.100.0.35:1102 (3 requests vs 10; multi-var leg + cross-area MW10 confirmed; falsification bit); code review APPROVED; ops findings: fixture profile baked into image (--build mandatory), /opt/otopcua-s7 root-owned (rsync needs sudo rsync-path); Timer/Counter not live-pinned (no TM/CT fixture area)"
},
{
"id": 14,
"subject": "Task 14: docker-dev live gate (regression of the merged remainder)",
"status": "completed",
"blockedBy": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
"note": "GATE PASSED (no commit): fresh rig @6b46c485, deploy sealed 306ms 6/6 Applied, 180s subscription 0 Bad across a REAL surgical UNS-ref apply (rebuild=False, surgicalRawTags=1), UNS mirror live, panels 200, 0 ERR/FTL, teardown clean. Anomalies non-blocking: first-POST 404 ~40s after boot (retry), boot telemetry reconnect warnings, ScriptLogEntry dead-letters on sites. Not exercised: historian E2E (VPN), FetchAndCache, ACL-enabled path, alarm ratchet"
},
{
"id": 15,
"subject": "Task 15: Close-out — full sweep + Outcomes",
"status": "completed",
"note": "PLACEHOLDER_SHA (docs) preceded by 7c5a843a (review-round comment fixes). Build 0 errors / 896 pre-existing warnings. Full unit sweep 43/43 projects green: 10358 passed, 0 failed, 19 skipped (all pre-existing env-gated; AdminUI.Tests 1537/0-skipped and Configuration.Tests 190/0-skipped confirm Task 7's SQL-backed pins RAN against 10.100.0.35,14330). No failures, so no isolation re-run. CLAUDE.md correction: the ACL decision cache is sharded per API key (ApiKeyId ?? Principal), not per session.",
"blockedBy": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14
]
}
],
"lastUpdated": "2026-08-17T08:00:00Z"
"closeOut": "All 15 tasks complete on branch perf/followups. Both release-condition LIVE GATES PASSED: S7 span reads vs the s7_1500 fixture at 10.100.0.35:1102 (57c66416 — 3 wire requests for 10 tags with spans on vs 10 with spans off; multi-var + cross-area confirmed; Timer/Counter exclusion NOT live-pinned, no TM/CT fixture area), and the docker-dev regression on the merged remainder (no commit — deploy sealed 306ms 6/6 Applied, a REAL surgical UNS-reference apply with rebuild=False streaming Good across an open 180s subscription, 0 ERR/FTL, clean teardown). Close-out sweep: build 0 errors, 43/43 test projects green, 10358 passed / 0 failed / 19 skipped (all pre-existing env-gated). 12 register items in docs/plans/2026-08-15-perf-roadmap-remainder.md §Follow-up register are annotated closed (append-only, with SHAs); HistorianGateway's cross-repo batch WriteLiveValues RPC and the synchronous NodeDeploymentState writes stay open. A NEW follow-up register for this round (ACL generation-rollover window, S7 cancellation/one-bad-decode coverage, historian hope-wait, AdminUI mid-run-blip + orphaned test DBs, MTConnect AsStream ownership, docker-dev first-POST 404) lives in the plan's ## Outcomes section — none blocking. Not exercised by either gate: historian E2E (needs VPN), FetchAndCache flip, ACL-enabled enforcement path, alarm-bearing surgical ratchet, load-scale throughput.",
"lastUpdated": "2026-08-17T15:05:00Z"
}