Phase 6.4 Stream D server-side - IdentificationFolderBuilder (OPC 40010 sub-folder materializer) #102

Merged
dohertj2 merged 1 commits from phase-6-4-stream-d-identification into v2 2026-04-19 11:59:38 -04:00
Owner

Closes the server-side / non-UI piece of Phase 6.4 Stream D. Razor IdentificationFields.razor Admin-UI editor ships when the Admin UI pass lands.

Summary

  • IdentificationFolderBuilder pure fn. Materializes the OPC 40010 Machinery Identification sub-folder per decision #139. Reads 9 nullable Equipment columns + emits one AddProperty per non-null field. Skips sub-folder entirely when all 9 null.
  • HasAnyFields(equipment) short-circuit.
  • FolderName + FieldNames constants.
  • ACL: sub-folder inherits Equipment ScopeId so Phase 6.2 trie treats them together.

Test plan

  • 9 new tests: HasAnyFields both branches; Build all-null skips folder; fully-populated emits 9 in decision #139 order; 3-of-9 emits only those three; YearOfConstruction short → Int32 widen; string round-trip; FieldNames + FolderName match spec.
  • Full solution dotnet test: 1202 passing (was 1193, +9).

🤖 Generated with Claude Code

Closes the server-side / non-UI piece of Phase 6.4 Stream D. Razor `IdentificationFields.razor` Admin-UI editor ships when the Admin UI pass lands. ## Summary - `IdentificationFolderBuilder` pure fn. Materializes the OPC 40010 Machinery Identification sub-folder per decision #139. Reads 9 nullable Equipment columns + emits one AddProperty per non-null field. Skips sub-folder entirely when all 9 null. - `HasAnyFields(equipment)` short-circuit. - `FolderName` + `FieldNames` constants. - ACL: sub-folder inherits Equipment ScopeId so Phase 6.2 trie treats them together. ## Test plan - [x] 9 new tests: HasAnyFields both branches; Build all-null skips folder; fully-populated emits 9 in decision #139 order; 3-of-9 emits only those three; YearOfConstruction short → Int32 widen; string round-trip; FieldNames + FolderName match spec. - [x] Full solution `dotnet test`: 1202 passing (was 1193, +9). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dohertj2 added 1 commit 2026-04-19 11:59:27 -04:00
Closes the server-side / non-UI piece of Phase 6.4 Stream D. The Razor
`IdentificationFields.razor` component for Admin-UI editing ships separately
when the Admin UI pass lands (still tracked under #157 UI follow-up).

Core.OpcUa additions:
- IdentificationFolderBuilder — pure-function builder that materializes the
  OPC 40010 Machinery companion-spec Identification sub-folder per decision
  #139. Reads the nine nullable columns off an Equipment row:
  Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision,
  YearOfConstruction (short → OPC UA Int32), AssetLocation, ManufacturerUri,
  DeviceManualUri. Emits one AddProperty call per non-null field; skips the
  sub-folder entirely when all nine are null so browse trees don't carry
  pointless empty folders.
- HasAnyFields(equipment) — cheap short-circuit so callers can decide
  whether to invoke Folder() at all.
- FolderName constant ("Identification") + FieldNames list exposed so
  downstream tools / tests can cross-reference without duplicating the
  decision-#139 field set.

ACL binding: the sub-folder + variables live under the Equipment node so
Phase 6.2's PermissionTrie treats them as part of the Equipment ScopeId —
no new scope level. A user with Equipment-level grant reads the
Identification fields; a user without gets BadUserAccessDenied on both the
Equipment node + its Identification variables. Documented in the class
remarks; cross-reference update to acl-design.md is a follow-up.

Tests (9 new IdentificationFolderBuilderTests):
- HasAnyFields all-null false / any-non-null true.
- Build all-null returns null + doesn't emit Folder.
- Build fully-populated emits all 9 fields in decision #139 order.
- Only non-null fields are emitted (3-of-9 case).
- YearOfConstruction short widens to DriverDataType.Int32 with int value.
- String values round-trip through AddProperty.
- FieldNames constant matches decision #139 exactly.
- FolderName is "Identification".

Full solution dotnet test: 1202 passing (was 1193, +9). Pre-existing
Client.CLI Subscribe flake unchanged.

Production integration: the component that consumes this is the
address-space-build flow that walks the live Equipment table + calls
IdentificationFolderBuilder.Build(equipmentFolder, equipment) under each
Equipment node. That integration is the remaining Stream D follow-up
alongside the Razor UI component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit eac457fa7c into v2 2026-04-19 11:59:38 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#102