{
  "planPath": "docs/plans/2026-06-17-historian-paging-udt-member-paths.md",
  "designPath": "docs/plans/2026-06-17-historian-paging-udt-member-paths-design.md",
  "designCommit": "ad66ecc9",
  "baseMaster": "c402872c",
  "mergeCommit": "09fecf4c",
  "branch": "feat/stillpending-historian-paging-udt-members (merged + deleted)",
  "scope": "Bundle two stillpending.md §2 items. WS-A (#400): page Raw history WITHIN an oversized tie-cluster timestamp via a bounded within-timestamp over-fetch (HistoryPaging.SliceTieCluster pure helper + MaxTieClusterOverfetch knob + ServeRawPaged stuck-branch rewrite) instead of the loud BadHistoryOperationUnsupported. WS-B AbCip: fan out controller-discovered UDTs into atomic-member Variables via the CIP template-instance-id → FetchUdtShapeAsync. WS-B TwinCAT: pure TwinCATSymbolExpander recursion (over a minimal ITwinCATSymbolNode abstraction) fed by the real ADS ISymbol tree in BrowseSymbolsAsync. NO Commons/wire/proto/EF change; NO bUnit.",
  "dependencyGraph": "{T1 ∥ T2 ∥ T3} → T4 → T5 → T6",
  "tasks": [
    {"id": 1, "subject": "WS-A #400: HistoryPaging.SliceTieCluster + MaxTieClusterOverfetch option + ServeRawPaged within-timestamp over-fetch + tests", "classification": "standard", "status": "completed", "commit": "2e6c6d3a", "reviewFixCommit": "0f929ae6", "review": "spec ✅; code APPROVE WITH MINOR. The stuck-branch over-fetches (T,T,MaxTieClusterOverfetch+1) explicit cap; cluster>bound preserves the loud BadHistoryOperationUnsupported. Both reviewers verified the broadened stall trigger (empty OR last-sample==resume-boundary, gated by backendFull) does NOT over-fire on distinct-timestamp pages. Review-fix 0f929ae6: defensive (uint)Math.Max(1,MaxTieClusterOverfetch)+1u (wrap-safe, never 0) + Validate() gating comment. Host ctor gained IConfiguration to bind ServerHistorian (Program.cs was out of touch-scope) — DI-safe (no test constructs the hosted service; live-proven on the rig). OpcUaServer 237 green."},
    {"id": 2, "subject": "WS-B AbCip: controller-discovered UDT member expansion (fetch shape + fan out atomic members, bounded depth) + tests", "classification": "standard", "status": "completed", "commit": "4e141402", "reviewFixCommit": "4a7b0fde", "review": "spec ✅; code CHANGES REQUESTED (CRITICAL) → FIXED + RE-REVIEWED APPROVE. The first commit's production path was INERT — CipSymbolObjectDecoder decoded the struct template-instance-id (lower 12 bits) then discarded it, so ResolveDiscoveredUdtShapeAsync always got null → always degraded to the placeholder (only the test seam worked). Fix 4a7b0fde threaded the id: decoder surfaces (uint?)typeCode for structs → AbCipDiscoveredTag.TemplateInstanceId (additive optional) → LibplctagTagEnumerator carries it → DiscoverAsync passes it (not null) → FetchUdtShapeAsync runs. Re-review CONFIRMED production top-level expansion functional via a no-seam test (asserts LastTemplateId==the id). NESTED struct members stay a documented deferral (Template Object member blocks carry no nested template id). Also: \\0-separator comment fix, lazy folders (no empty nested sub-folder), strengthened read-path test. AbCip 299 green."},
    {"id": 3, "subject": "WS-B TwinCAT: pure TwinCATSymbolExpander recursion + real BrowseSymbolsAsync ISymbol adapter + tests", "classification": "standard", "status": "completed", "commit": "3699fc16", "reviewFixCommit": "fdd6b332", "review": "spec ✅; code APPROVE WITH MINOR. Pure ExpandLeaves over a minimal ITwinCATSymbolNode (MaxDepth=8, HashSet dedup by InstancePath, drop null-mapped leaves); real BrowseSymbolsAsync adds an AdsSymbolNode : ITwinCATSymbolNode adapter (IsStruct=Category==Struct, Children=SubSymbols, Mapped=MapSymbolType) and feeds the expander; cancellation-throw + Flat load mode + MapSymbolType unchanged; pre-declared Structure reject untouched. Review-fix fdd6b332: updated the stale ITwinCATClient.BrowseSymbolsAsync doc, cached AdsSymbolNode fields, added a Flat-vs-VirtualTree live-follow-up comment. TwinCAT 168 green. The real ISymbol recursion is operator-gated (no local ADS); the pure expander is the unit proof."},
    {"id": 4, "subject": "Docs (Historian.md paging-limitation→resolved + AbCip.md/TwinCAT.md UDT member sections) + §2 clear via plan record only", "classification": "small", "status": "completed", "commit": "163f57b6", "review": "Historian.md paging-limitation block rewritten as resolved (within-timestamp paging + MaxTieClusterOverfetch knob/table + short-page-with-CP nuance + >ceiling loud-fail); docs/drivers/AbCip.md + TwinCAT.md UDT member sections added. No overclaim — all deferrals stated (member writes, whole-UDT container=BadNotSupported, AbCip nested-struct-in-production, TwinCAT Flat-mode caveat). stillpending.md NOT staged. Verified accurate by the controller."},
    {"id": 5, "subject": "Full build + OpcUaServer/AbCip/TwinCAT tests + final integration review", "classification": "standard", "status": "completed", "reviewFixCommit": "09fecf4c", "review": "Build 0 errors (359 pre-existing warnings). OpcUaServer 237 / AbCip 299 / TwinCAT 168 green. Final integration review = SHIP WITH FOLLOW-UPS, all 6 invariants PASS. The one actionable Minor (stale comment in the untouched TwinCATDriverFactoryExtensions.cs:92 — claimed structs 'never appear in the discovered address space', now wrong) fixed in 09fecf4c. Minor-2 (discovered-member writes optimistic-Good then revert — pre-existing pattern for ALL discovered nodes, handled by write-outcome self-correction; member writes documented as deferred) and Minor-3 (the 2nd stall shape fires one page early — benign perf, always correct) recorded as follow-ups, no change."},
    {"id": 6, "subject": "Live /run best-effort + finish branch (merge to master + push) + memory", "classification": "standard", "status": "completed", "live": "WS-A #400 unit+integration-proven (the oversized-tie integration test pages the full cluster with no dup/skip + the >bound backstop test; a real anomaly needs the AVEVA historian on 10.100.0.48 — infra-gated). WS-B AbCip unit-proven incl. the no-seam production-path test (ab_server ControlLogix UDT support is partial — not exercised live). WS-B TwinCAT operator-gated (Windows-only ADS) — unit-proven via the expander. LIVE SMOKE PASS: rebuilt docker-dev central-1 from the branch → 0 unhandled exceptions, OPC UA server started on :4840, Phase7Applier applied plan (added=91, rebuild=True) + full materialisation (areas=3/lines=8/equipment=40, alarms=1, tags=5, vtags=42), and the new IConfiguration Host ctor bound cleanly — proves the new code + the one runtime-DI change load + serve with no startup regression. Merged ff to master 09fecf4c + pushed; branch deleted."}
  ],
  "executionState": "COMPLETE — merged to master + pushed (09fecf4c). #400 within-timestamp tie-cluster paging + AbCip/TwinCAT controller-discovered UDT member expansion shipped. Build 0 errors; OpcUaServer 237 / AbCip 299 / TwinCAT 168 green. NO contract/wire/proto/EF change; NO bUnit.",
  "reviewFollowUps": [
    "AbCip NESTED struct members in production are a documented deferral — the CIP Template Object member block carries no nested template id, so a nested struct's sub-shape can't be re-fetched from its parent (only the test seam supplies it). Top-level discovered UDT members ARE functional in production. Nested members are dropped (never mis-emitted). Closeable only with a controller-side per-member nested-type query.",
    "TwinCAT discovered-member expansion is operator-gated live (Windows-only ADS). The real ISymbol SubSymbols recursion is unit-proven via the pure expander. Live FOLLOW-UP: if a TC3 Flat-mode browse does NOT populate ISymbol.SubSymbols on struct symbols, members won't surface → switch SymbolsLoadMode.Flat→VirtualTree (deliberately NOT changed now to avoid regressing the Phase 4c array discovery; needs a real ADS target to verify).",
    "Discovered AbCip/TwinCAT member nodes with a writable parent advertise CurrentReadWrite but member WRITES are deferred — an authorized write gets optimistic-Good then reverts (the node isn't in DriverHostActor's reverse map). Pre-existing pattern for ALL discovered nodes; the write-outcome self-correction handles the revert (no phantom Good sticks). Closeable when discovered tags become authorable equipment tags.",
    "WS-A: the broadened stall trigger fires one page earlier than strictly necessary for clusters straddling the page-cap boundary (one extra (T,T,cap) over-fetch). Benign perf, always correct (integration-test-verified). No action."
  ],
  "lastUpdated": "2026-06-17"
}
