From b990adae55eaf5b37d5493b002cf2f135d0d056f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 10 Aug 2026 05:48:09 -0400 Subject: [PATCH] docs(plans): close F10b + SetSeverity reconsider gates as decided-against (user-confirmed 2026-08-10); open plan tasks 3 -> 1 --- deferment.md | 23 +++++++++++-------- docs/plans/2026-06-19-followups-batch.md | 8 +++---- .../2026-06-19-followups-batch.md.tasks.json | 12 ++++++---- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/deferment.md b/deferment.md index 221f174a..f37496e9 100644 --- a/deferment.md +++ b/deferment.md @@ -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`). --- diff --git a/docs/plans/2026-06-19-followups-batch.md b/docs/plans/2026-06-19-followups-batch.md index 11710985..a412f9df 100644 --- a/docs/plans/2026-06-19-followups-batch.md +++ b/docs/plans/2026-06-19-followups-batch.md @@ -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. diff --git a/docs/plans/2026-06-19-followups-batch.md.tasks.json b/docs/plans/2026-06-19-followups-batch.md.tasks.json index f566051c..14f0bef8 100644 --- a/docs/plans/2026-06-19-followups-batch.md.tasks.json +++ b/docs/plans/2026-06-19-followups-batch.md.tasks.json @@ -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" -} \ No newline at end of file +}