1e02661598
The two items deferment.md §0.1 named as the remaining untracked engineering. adminui-followups (19 pending / 0 completed) — CLOSED by the overlap audit the plan itself asks for first, with no code written. All 19 tasks are shipped, superseded by v3, or moot. WS1's premise had also expired: it assumed driver-embedded tag lists in DriverConfig are the runtime source of truth, which v3 ended (the artifact populates RawTags). Its capability shipped in the v3 shape — TagConfigEditorMap + DeviceFormMap, both verified reachable from RawTagModal/DeviceModal rather than merely present. Live gates on docker-dev (image rebuilt: the rig was running a build 3 h older than the last code commit; parity asserted per container plus a positive check that the symbols under test are in the shipped DLLs): - R2-03 T11 PASSED — build 0 errors, Runtime.Tests 555/555, RoslynVirtualTagEvaluator 20/20. - R2-03 T12 PASSED — VT 42 -> 0x80020000 -> 42, staged WITHOUT a deployment (a Modbus register flipped on the fixture) so the VT child survived; a deploy-driven trip creates a fresh child with no last value, which is correct-by-design and masked the result twice. P7 confirmed: two byte-identical redeploys, zero disturbance. - driver-reconfigure Task 3 PASSED — a faulted driver adopted a corrected config and connected with the node's startedAt unchanged and RestartCount=0. - R2-10 T10 still OPEN, but the recorded cause was wrong. Not ConnectionBackoff throttling (the breaker opens on 2 failures / 30 s): the dead-endpoint failure mode never enters the resilience pipeline. IDriver.InitializeAsync is not invoker-wrapped (DriverInstanceActor.cs:707) and ModbusDriver swallows per-tag failures in BOTH directions, so even the plan's fallback trigger cannot work. Live-proven: a driver timing out every read against a blackhole reported ConsecutiveFailures=0 while the panel read "All targets healthy." Finding from T12: the Bad publish's documented "carrying the last-known value" is unobservable by any client — subscription and a raw Read service call both return NULL. The actor is correct; every existing failure assertion checked StatusCode alone, so the value half was asserted nowhere. Adds VirtualTagActorTests.The_Bad_result_carries_the_last_known_value, which proves the actor emits it. The documentation is what overstates.
47 lines
7.7 KiB
JSON
47 lines
7.7 KiB
JSON
{
|
|
"planPath": "docs/plans/2026-05-29-adminui-followups.md",
|
|
"branch": "feat/adminui-followups",
|
|
"lastUpdated": "2026-08-07",
|
|
"closureNote": "CLOSED 2026-08-07 by the overlap audit deferment.md §6.2 asked for before executing. This file recorded 19 pending / 0 completed and was called 'the largest unexecuted plan in the tree' and 'the only substantial engineering left' — that was bookkeeping drift, not backlog. Every task is shipped, superseded by v3, or moot; per-task evidence is in each closureNote below. The plan's own premise died with v3: WS1 rests on 'driver-embedded tag/device lists in DriverConfig JSON are the runtime source of truth', but v3 authors tags as Tag rows in /raw and the deploy artifact populates the driver's RawTags (ModbusDriverFactoryExtensions.cs:98-102), so hand-editing a list on the driver form no longer decides what the driver polls. The capability WS1 wanted was delivered in the v3 shape instead: per-driver typed tag editors via TagConfigEditorMap (11 editors, dispatched from RawTagModal.razor:79 + RawManualTagEntryModal.razor:92) and per-driver device forms via DeviceFormMap (9 forms, dispatched from DeviceModal.razor:45), both guarded by DriverFormMapParityTests + DriverDispatchMapParityTests. Verified reachable, not merely present — a supersession claim naming an unreachable replacement would repeat the dead-surface class this repo has hit repeatedly.",
|
|
"tasks": [
|
|
{"id": 11, "plan": 1, "subject": "Task 1: Generic CollectionEditor<TRow> component", "status": "completed",
|
|
"closureNote": "SHIPPED — Components/Shared/Drivers/CollectionEditor.razor exists with the designed shape (working-copy modal, commit-on-save, NewRow factory, Validate delegate)."},
|
|
{"id": 12, "plan": 2, "subject": "Task 2: Modbus tag editor (proof) + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — delivered as Uns/TagEditors/ModbusTagConfigEditor.razor via TagConfigEditorMap, not as a CollectionEditor over an embedded JSON list. The embedded list it would have edited is no longer the runtime source of truth."},
|
|
{"id": 13, "plan": 3, "subject": "Task 3: AbCip device+tag editors + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — DeviceForms/AbCipDeviceForm.razor + Uns/TagEditors/AbCipTagConfigEditor.razor."},
|
|
{"id": 14, "plan": 4, "subject": "Task 4: AbLegacy device+tag editors + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — DeviceForms/AbLegacyDeviceForm.razor + Uns/TagEditors/AbLegacyTagConfigEditor.razor."},
|
|
{"id": 15, "plan": 5, "subject": "Task 5: TwinCAT device+tag editors + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — DeviceForms/TwinCATDeviceForm.razor + Uns/TagEditors/TwinCATTagConfigEditor.razor."},
|
|
{"id": 16, "plan": 6, "subject": "Task 6: FOCAS device+tag editors + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — DeviceForms/FocasDeviceForm.razor + Uns/TagEditors/FocasTagConfigEditor.razor."},
|
|
{"id": 17, "plan": 7, "subject": "Task 7: S7 tag editor + tests", "status": "completed",
|
|
"closureNote": "SUPERSEDED — Uns/TagEditors/S7TagConfigEditor.razor (plus DeviceForms/S7DeviceForm.razor, which this task did not ask for)."},
|
|
{"id": 18, "plan": 8, "subject": "Task 8: OpcUaClient endpoint-URL editor + tests", "status": "completed",
|
|
"closureNote": "SHIPPED — and it is the one task delivered in the plan's own shape: Forms/OpcUaClientDriverForm.razor is the sole production consumer of CollectionEditor. Endpoints genuinely still live in the driver config, which is why this one survived the v3 change and Tasks 2-7 did not."},
|
|
{"id": 19, "plan": 9, "subject": "Task 9: ResilienceFormModel + tests", "status": "completed",
|
|
"closureNote": "SHIPPED — Components/Shared/Drivers/ResilienceFormModel.cs + tests/Server/...AdminUI.Tests/ResilienceFormModelTests.cs."},
|
|
{"id": 20, "plan": 10, "subject": "Task 10: Typed resilience form in DriverResilienceSection", "status": "completed",
|
|
"closureNote": "SHIPPED — DriverResilienceSection.razor binds ResilienceFormModel and keeps the raw-JSON escape hatch at :67. NARROWER than designed, deliberately: two of the designed fields no longer exist. RecycleIntervalSeconds was deleted with the scheduled-recycle machinery (Gitea #522; see the tombstone comments at DriverResilienceOptions.cs:22 and ResilienceFormModel.cs:26) and the bulkhead knobs were deleted end-to-end by archreview R2-02 because Build never added a concurrency limiter. Authoring either today would re-create an inert knob."},
|
|
{"id": 21, "plan": 11, "subject": "Task 11: RoleMapper.Merge overload + tests", "status": "completed",
|
|
"closureNote": "SHIPPED — RoleMapper.Merge(baseline, dbRows) consumed by OtOpcUaGroupRoleMapper.MapAsync; covered by OtOpcUaGroupRoleMapperTests + CanonicalAdminRolesTests."},
|
|
{"id": 22, "plan": 12, "subject": "Task 12: Register ILdapGroupRoleMappingService in DI", "status": "completed",
|
|
"closureNote": "SHIPPED — Configuration/ServiceCollectionExtensions.cs:34 (EF-backed, Scoped). A driver-only node additionally binds NullLdapGroupRoleMappingService (Host/Program.cs:351) because mesh Phase 4 left it with no ConfigDb."},
|
|
{"id": 23, "plan": 13, "subject": "Task 13: Wire DB merge into AuthEndpoints.LoginAsync", "status": "completed",
|
|
"closureNote": "SHIPPED — AuthEndpoints.cs:100 calls roleMapper.MapAsync behind the shared IGroupRoleMapper<string> seam, which applies the appsettings GroupToRole baseline and merges system-wide DB grants."},
|
|
{"id": 24, "plan": 14, "subject": "Task 14: Add FleetAdmin authorization policy", "status": "completed",
|
|
"closureNote": "SHIPPED — AdminUiPolicies.FleetAdmin registered at Security/ServiceCollectionExtensions.cs:160 (RequireRole Administrator; the policy NAME was kept stable across the AdminRole rename)."},
|
|
{"id": 25, "plan": 15, "subject": "Task 15: RoleGrants.razor global CRUD (FleetAdmin-gated)", "status": "completed",
|
|
"closureNote": "SHIPPED — RoleGrants.razor:2 carries [Authorize(Policy = AdminUiPolicies.FleetAdmin)]; Add (:132 CreateAsync, IsSystemWide=true/ClusterId=null as designed) + Delete (:148) + the read-only appsettings fallback panel. No in-place Update, which is correct rather than missing: a row is only (LdapGroup, Role), so editing either field IS a different grant — delete-then-add is the whole of 'update' here."},
|
|
{"id": 26, "plan": 16, "subject": "Task 16: LdapGroupRoleMapping service tests (global CRUD)", "status": "completed",
|
|
"closureNote": "SHIPPED — tests/Core/...Configuration.Tests/LdapGroupRoleMappingServiceTests.cs."},
|
|
{"id": 27, "plan": 17, "subject": "Task 17: Delete stale source comments", "status": "completed",
|
|
"closureNote": "MOOT — the comments went with their files. FleetStatusHub.cs was deleted with the whole zero-client fleet-status channel (Gitea #542); DriverEditRouter.razor, DriverTypePicker.razor and DriverEdit.razor were deleted by v3 Batch 2, which retired the routed /clusters/{id}/drivers flow. Greps for the named comments (TODO(3.3/3.4), 'legacy DriverEdit', 'until the bridge lands') return nothing outside bin/obj."},
|
|
{"id": 28, "plan": 18, "subject": "Task 18: Strip now-true rendered notes", "status": "completed",
|
|
"closureNote": "MOOT — same cause: the driver pages that rendered 'list-editor coming in a follow-up phase' and 'typed-form-ifying Polly is a follow-up' no longer exist. Neither string survives anywhere in src/."},
|
|
{"id": 29, "plan": 19, "subject": "Task 19: Full verification (build + test + /run)", "status": "completed",
|
|
"closureNote": "DONE 2026-08-07 as part of the same session's live-gate run: solution build 0 errors, and the AdminUI surfaces this plan targets were driven on the docker-dev rig alongside the four live gates. See deferment.md §9."}
|
|
]
|
|
}
|