docs(plans): close F10b + SetSeverity reconsider gates as decided-against (user-confirmed 2026-08-10); open plan tasks 3 -> 1

This commit is contained in:
Joseph Doherty
2026-08-10 05:48:09 -04:00
parent 0319cf5de2
commit b990adae55
3 changed files with 25 additions and 18 deletions
+14 -9
View File
@@ -58,7 +58,7 @@ So everything below is **untracked work**. Nothing in it is a data-loss or outag
| ~~**G-7 residual** — Sql / MTConnect / Calculation / OpcUaClient have no Contracts-side `Inspect`~~ ✅ **CLOSED 2026-08-09** (`2947a2b9` + `1469fbb4`, merge `0695f682`). All four now ship a Contracts-side `*TagDefinitionFactory.Inspect` **wrapping the runtime parse, never mirroring the AdminUI model** — Sql wraps the pre-existing `SqlEquipmentTagParser`; the MTConnect / OpcUaClient / Calculation blob readers were **moved** into their Contracts assemblies (the drivers now call them), so never-stricter-than-runtime is structural. `Driver.Calculation.Contracts` was created (no Contracts assembly existed). The reflection coverage guard was falsified (map entry removed → red naming `SqlTagDefinitionFactory`), and `TagConfigAuthoringDeployParityTests` pins authoring-accepts ⇒ deploy-accepts. **Galaxy is now the sole unmapped type, deliberately + symmetrically** (no typed authoring editor either). ~~Found en route, left open: `MTConnectTagConfigModel` reads `dataType` leniently (misspelled name → silently `String`) while the runtime rejects it — authoring weaker than runtime, the *opposite* direction from G-7; documented inline in the parity tests.~~ ✅ Filed and FIXED same day as **#549** — and it was 6 models wide, not 1 (see the "2026-08-09 (later)" §9 entry); residuals **#550**/**#551** are ✅ **CLOSED 2026-08-10** (merges `b50523b1` + `333ea488`, see the 2026-08-10 §9 entry) — their own residuals are **#552** (Sql browse-commit emits a config the gate rejects) / **#553** (CSV typed-column merge silently rewrites typo'd enums pre-gate). | ~~The deploy gate is weaker than the authoring gate.~~ Config arriving by CSV import or API now meets the same checks the modal applies. | §1.2 |
| ~~**3 carried-open archreview findings** — the failure-visibility trio~~ **Update 2026-08-07 (truth sweep): mostly CLOSED.** 01/S-1 **FIXED** — `AddressSpaceApplier.cs:95-97` tallies swallowed removal-pass failures into `AddressSpaceApplyOutcome.FailedNodes` (comment cites 01/S-1). 03/S4 **FIXED** — `PrimaryGatePolicy.cs` default-denies on unknown role when a driver peer exists (doc-comment "Fixes archreview 03/S4"), live-proven R2-04 T15. 06/S-1 **PARTIAL** — fail-closed advise path + `galaxy.writes.unconfirmed` meter shipped in `GatewayGalaxyDataWriter.cs`; residual = gateway-side WriteComplete correlation (cross-repo, mxaccessgw). **Update 2026-08-09: the gateway half SHIPPED** — mxaccessgw main `b948e69` correlates the worker's `OnWriteComplete` onto the unary `WriteSecured` reply (`statuses[0]`; bounded wait default **1.5 s** — deliberately under OtOpcUa's 2 s Tier A Write budget, configurable via `MxGateway:Worker:WriteCompletionWaitMilliseconds`; timeout falls back to today's empty-statuses shape, still metered unconfirmed). **Update 2026-08-09 (later): 06/S-1 CLOSED — live-gated end to end, and the gate found + fixed three defects.** The user-authorized redeploy exposed (1) the correlation covered `WriteSecured` only while OtOpcUa's dominant no-login path emits plain `Write` — fixed gateway-side (worker `53f69cd`, all four unary write kinds, redeployed both hosts); (2) OtOpcUa's `StatusCodeMap.FromMxStatus` truncated the correlated failure row's MxStatus detail code to a byte (`1007 & 0xFF = 239` → OPC DA Good band), mapping a REFUSED write to Good — fixed with the not-IsSuccess-never-Good invariant + category routing (`SECURITY_ERROR → BadUserAccessDenied`); (3) the writer's borrow-from-subscription-registry optimization could never commit (a subscription handle is plain-advised; MXAccess refuses the no-login write `SECURITY_ERROR 1008` AND the advise upgrade `E_INVALIDARG` — one advise per HANDLE, not per attribute) — borrow path REMOVED, writer always owns a supervisory-advised handle. Live legs all PASSED on windev: happy write Good off confirmed `statuses[0]` + both meters flat; read-only refusal surfaces Bad-family + node-revert input; `WriteCompletionWaitMilliseconds=0` control → provisional Good + metered unconfirmed (override removed after). Pinned by `Live_correlated_write_statuses_confirm_commit_and_surface_refusal`, `Live_write_to_subscribed_tag_commits_via_own_handle`, `GatewayGalaxyWriteMeterLiveTests`, and the new `StatusCodeMapTests` rows. | ~~Residual is only the 06/S-1 live leg~~ **CLOSED 2026-08-09** — nothing remains. | §6.3 |
| ~~**1 genuinely unexecuted plan** — `2026-05-29-adminui-followups`, **19 pending / 0 completed**~~ ✅ **CLOSED 2026-08-07 by the overlap audit it asked for — it was bookkeeping drift, not backlog.** All 19 tasks are shipped, superseded by v3, or moot; **no code was needed.** The warning was right and then some: WS1's whole *premise* died with v3 (driver-embedded tag lists in `DriverConfig` are no longer the runtime source of truth — the artifact populates `RawTags`), and its capability was delivered in the v3 shape (`TagConfigEditorMap` + `DeviceFormMap`, both verified **reachable**, not merely present). WS2/WS3 shipped; WS4's target files were deleted by v3 Batch 2 + #542. Per-task evidence in the `.tasks.json`. | §6.2 |
| ~~**11 other open plan tasks**~~ **3 as of 2026-08-10** — ~~5 akka-hosting partials (likely v3-superseded)~~ *(verified superseded 2026-08-07 — F8/F9/F10/F13/F14 marked completed; `Phase7Composer`/`ScriptedAlarmActor` deleted, evaluators/SDK sinks/security policies all production-wired)*, ~~4+1 followups-batch (open *by choice*)~~ *(2026-08-10: Tasks 9 + 10 VERIFIED PASS live — Int64 authoring end-to-end on the rebuilt rig; probe E2E 12/12 with the "needs Windows-VM" premise corrected to TCP-reachability gating)*, ~~fixedtree Task 11~~ *(closed 2026-08-10: obsolete — the injection feature it gated was deleted with #507)*, ~~1 manual smoke checklist~~ *(closed 2026-08-10: obsolete — the driver pages it smokes were retired in v3 Batch 2)*. **Remaining 3: F10b + SetSeverity (reconsider gates, decisions already taken — do not build without a fresh go-ahead) and the device-gated proofs (hardware-blocked).** | Small, and two of the groups are decisions already taken. | §6.2, §9 2026-08-10 (sixth) |
| ~~**11 other open plan tasks**~~ **3 as of 2026-08-10** — ~~5 akka-hosting partials (likely v3-superseded)~~ *(verified superseded 2026-08-07 — F8/F9/F10/F13/F14 marked completed; `Phase7Composer`/`ScriptedAlarmActor` deleted, evaluators/SDK sinks/security policies all production-wired)*, ~~4+1 followups-batch (open *by choice*)~~ *(2026-08-10: Tasks 9 + 10 VERIFIED PASS live — Int64 authoring end-to-end on the rebuilt rig; probe E2E 12/12 with the "needs Windows-VM" premise corrected to TCP-reachability gating)*, ~~fixedtree Task 11~~ *(closed 2026-08-10: obsolete — the injection feature it gated was deleted with #507)*, ~~1 manual smoke checklist~~ *(closed 2026-08-10: obsolete — the driver pages it smokes were retired in v3 Batch 2)*. ~~**Remaining 3: F10b + SetSeverity (reconsider gates, decisions already taken — do not build without a fresh go-ahead) and the device-gated proofs (hardware-blocked).**~~ **Remaining 1 as of 2026-08-10 (later that day): F10b + SetSeverity reconsider gates put to the user and CLOSED decided-against (F10b re-open trigger: operator complaints about rebuild churn on data-type edits; SetSeverity is operationally invisible — engine overwrites on first eval); the device-gated proofs bundle trimmed to its 2 genuinely-gated sub-items (H6 native-ack→AVEVA + AbLegacy/TwinCAT/FOCAS probes).** | Hardware-blocked only. | §6.2, §9 2026-08-10 (sixth) |
| **Review-coverage gap** — 10 shipped projects never reviewed (Calculation, Historian.Gateway, Mqtt +Browser/Contracts, MTConnect +Contracts, Sql +Browser/Contracts) while **3 modules still review the retired Wonderware backend** | Re-verified 2026-08-04 against `code-reviews/`. Unchanged since the audit. | §6.3 |
| ~~**`RawBrowseModal` stale-open race** (added 2026-08-05) — `OnParametersSetAsync` never re-checks staleness after its awaits, so a superseded open's late resolve can install the wrong device's browse config. The identical shape was found by review and **fixed** in `RawTagModal`/`RawManualTagEntryModal` (post-await staleness re-check; see `docs/plans/2026-08-05-raw-modal-picker-live-config-design.md` §6); the browse modal was deliberately left out of that branch's scope.~~ ✅ **CLOSED 2026-08-09** (`87637e4e`, merge `0739f4b9`) — the sibling `_openGeneration` guard ported to every post-await write, **plus two departures the siblings did not need**: this modal resolves inside `try/finally`, so the `catch` (a stale open's failure would paint its error over the live device) and the `finally` (a stale `return` still runs the tail, dropping the LIVE open's spinner) are generation-guarded too. Four `RawBrowseModalStaleOpenTests` facts, each observed red pre-guard and each re-falsified by reverting only its own guard (1-failed/3-passed per revert — no redundant pin). | ~~Lower stakes than the fixed pair… but the fix pattern is proven and cheap to port.~~ Ported. | design doc §6 |
@@ -1313,14 +1313,19 @@ against the current tree, then one Opus agent ran the two runnable verifies. No
no driver pages exist under `Components/Pages/`. The form bodies live on inside the `/raw`
modals, live-gated repeatedly across the 2026-07/08 rounds.
**Open plan tasks are now 3**, all deliberate: F10b surgical DataType/IsArray + alarm-severity
`SetSeverity` (reconsider gates — premises re-verified still true on the current tree:
`TagDeltaIsSurgicalEligible` still rebuild-forces DataType/IsArray; the node manager still projects
engine-computed severity) and the device-gated proofs (hardware-blocked; ~~two sub-items possibly
part-covered by the 2026-06-27 historian gate and `dcb0be65`, worth trimming when next touched~~
*trimmed 2026-08-10: Phase C T7 (backend retired; HistoryRead-vs-AVEVA proven by the cutover gate +
#491 read-back) and Phase B T9 (equivalent proof live via `dcb0be65`) both removed — the bundle is
now H6 native-ack→AVEVA + the AbLegacy/TwinCAT/FOCAS probe happy-paths, all genuinely gated*).
~~**Open plan tasks are now 3**~~ **now 1** (updated later 2026-08-10), all deliberate: ~~F10b
surgical DataType/IsArray + alarm-severity `SetSeverity` (reconsider gates — premises re-verified
still true on the current tree: `TagDeltaIsSurgicalEligible` still rebuild-forces DataType/IsArray;
the node manager still projects engine-computed severity)~~ *both reconsider gates were put to the
user later that day and CLOSED decided-against — F10b: the rebuild path is correct and the surgical
swap buys a rare edit at the cost of a client-visible atomicity window + ModelChangeEvent surface +
a mandatory live gate (re-open trigger: operator complaints about rebuild churn on data-type edits);
SetSeverity: operationally invisible, the engine overwrites authored severity on first eval* — and
the device-gated proofs (hardware-blocked; ~~two sub-items possibly part-covered by the 2026-06-27
historian gate and `dcb0be65`, worth trimming when next touched~~ *trimmed 2026-08-10: Phase C T7
(backend retired; HistoryRead-vs-AVEVA proven by the cutover gate + #491 read-back) and Phase B T9
(equivalent proof live via `dcb0be65`) both removed — the bundle is now H6 native-ack→AVEVA + the
AbLegacy/TwinCAT/FOCAS probe happy-paths, all genuinely gated*).
Side effect worth knowing: **the docker-dev rig now runs current master** (`6e7028e0856f`).
---
+4 -4
View File
@@ -76,11 +76,11 @@ build/test/rig. Finish a batch = ff-merge to master + push.
3. If any sub-part balloons >~300 LOC, split it out. Build; OpcUaServer + Runtime tests green.
4. Commit `feat(opcua): emit Bad blip + AuditWriteUpdateEvent + sync fail-fast on failed device write`.
### Task 7 (B4): F10b surgical DataType/IsArray in-place writes — **RECONSIDER GATE**
**Classification:** standard · **Do NOT build without an explicit fresh go-ahead** (previously decided against as dirty — brief value-type mismatch, no ModelChangeEvents, rare edits). If approved: extend `ISurgicalAddressSpaceSink.UpdateTagAttributes` to swap DataType/ValueRank in place + emit ModelChangeEvents; widen `Phase7Applier.TagDeltaIsSurgicalEligible`; **live-`/run` the rebuild=False path** (the prod-inertness trap, see the F10b deferred-wrapper lesson). Until approved this stays a deferred record.
### Task 7 (B4): F10b surgical DataType/IsArray in-place writes — **CLOSED: DECIDED AGAINST (2026-08-10)**
**Classification:** standard · Previously decided against as dirty (brief value-type mismatch, no ModelChangeEvents, rare edits); the reconsider gate was put to the user 2026-08-10 and the decision confirmed: **do not build.** The rebuild path handles DataType/IsArray edits *correctly*, just heavily; the surgical path would buy a rare authoring correction at the cost of a client-visible atomicity window (cached DataType vs. last published value), new ModelChangeEvent spec surface, and a mandatory live-`/run` of the rebuild=False path (the prod-inertness trap). **Re-open trigger:** operators complaining about rebuild/subscription churn on data-type edits — that evidence, and nothing weaker, reopens this.
### Task 8 (B5): Alarm-severity `SetSeverity` surgical update — **RECONSIDER GATE**
**Classification:** small · **Do NOT build without an explicit fresh go-ahead** (operationally invisible — the alarm engine overwrites authored severity on first eval). Recorded so the decision isn't a silent gap.
### Task 8 (B5): Alarm-severity `SetSeverity` surgical update — **CLOSED: DECIDED AGAINST (2026-08-10)**
**Classification:** small · Reconsider gate put to the user 2026-08-10; decision confirmed: **do not build.** Operationally invisible — the alarm engine overwrites authored severity with its computed value on the condition's first evaluation (re-verified on the current tree: `OtOpcUaNodeManager` still projects engine-computed severity), so the surgical write would be observable only between deploy and the next transition.
### Task 9 (C1): Modbus-Int64 full live authoring — **VERIFY-ONLY (operator/rig)**
**Classification:** verify · Seed a Modbus driver on docker-dev → sim `10.100.0.35:5020`, author an Int64 equipment tag, deploy, confirm the OPC UA node advertises `DataTypeIds.Int64` + reads changing. No code unless a gap surfaces.
@@ -133,18 +133,20 @@
"ref": "B4",
"subject": "F10b surgical DataType/IsArray in-place writes",
"classification": "standard",
"status": "pending",
"status": "closed",
"gate": "RECONSIDER \u2014 previously decided against (dirty); do NOT build without explicit go-ahead",
"group": "B-reconsider"
"group": "B-reconsider",
"notes": "CLOSED 2026-08-10: decided against, user-confirmed. Rebuild path is correct (just heavy); surgical swap adds a client-visible atomicity window + ModelChangeEvent surface + mandatory live-gate for a rare edit. Re-open trigger: operator complaints about rebuild churn on data-type edits."
},
{
"id": 8,
"ref": "B5",
"subject": "Alarm-severity SetSeverity surgical update",
"classification": "small",
"status": "pending",
"status": "closed",
"gate": "RECONSIDER \u2014 operationally invisible (alarm engine overwrites); do NOT build without explicit go-ahead",
"group": "B-reconsider"
"group": "B-reconsider",
"notes": "CLOSED 2026-08-10: decided against, user-confirmed. Operationally invisible - the alarm engine overwrites authored severity on first eval (re-verified: OtOpcUaNodeManager still projects engine-computed severity)."
},
{
"id": 9,
@@ -176,4 +178,4 @@
}
],
"lastUpdated": "2026-06-19"
}
}