v3 Batch 1 — greenfield schema + RawPath identity rewiring #469
Reference in New Issue
Block a user
Delete Branch "v3/batch1-schema-identity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
v3 Batch 1 — greenfield schema + RawPath identity rewiring
Branch:
v3/batch1-schema-identity→masterPlan:
docs/plans/2026-07-15-v3-batch1-schema-identity-plan.md· Design:docs/plans/2026-07-15-raw-uns-two-subtree-v3-design.mdGreenfield, compatibility-breaking. The config schema and every identity seam now speak RawPath
(
<Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Tag>). The full solution builds and all macOS-safetests pass; the OPC UA address space is deliberately dark (drivers connect, no tag variables
materialize) until Batch 4.
What changed
RawFolder/TagGroup/UnsTagReference; reshapedDriverInstance(−
NamespaceId, +RawFolderId?),Tag(raw-only: reqDeviceId, +TagGroupId?,−
DriverInstanceId/EquipmentId/FolderPath, keptWriteIdempotent),Equipment(−driver/devicebinding);
Namespace/NamespaceKind/EquipmentImportBatchretired. Single squashedV3Initialmigration (stored procs audited — the vestigial generation-model procs were rewritten to v3-valid
stubs;
sp_ComputeGenerationDiffdiffs the new tables).Commons/Types/RawPaths.cshelper;TagConfigIntentshedsFullName;EquipmentTagRefResolver<TDef>is byName(RawPath)-only (blob-fallback deleted); newRawTagEntry(RawPath, TagConfig, WriteIdempotent, DeviceName)delivery record. All 8 driversre-keyed: the per-driver
*EquipmentTagParserbecame*TagDefinitionFactory.FromTagConfig, optionsTags→RawTags,TagConfigkey normalization (nodeId/attributeRef;deviceHostAddressdropped).
DriverDeviceConfigMerger(Commons) merges DriverConfig + per-deviceDeviceConfig; multi-device drivers reconstruct their
Devices[]from Device rows; the artifact injectsthe merged config +
rawTagsat spawn.ConfigComposersnapshots the new tables;DeploymentArtifactflattens per-driverRawTagEntrylists keyed by RawPath (viaRawPathResolver) + emits an empty (dark) equipment-tagplan set;
AddressSpaceComposer/walker compose folders + VirtualTags + ScriptedAlarms only;DriverHostActormaps re-keyed to RawPath;DraftValidatorgainsRawNameInvalid/HistorianTagnameTooLong/UnsEffectiveNameCollision.pre-declared Tags editor + NamespaceId;
UnsTreeServicetag-authoring returns "moved to the Raw tree(/raw) in v3 Batch 2". (Batch 2/3 build the real
/raw+ reference UI.)Verification gate (all four items)
Core 260, Core.Abstractions 134, Configuration 107, Runtime 355 (42 dark-Batch-4 skips),
ControlPlane 78, OpcUaServer 335 (4 skips), AdminUI 507 (3 skips), + all 8 driver unit suites
(Modbus 315, S7 264, AbCip 342, AbLegacy 213, TwinCAT 192, FOCAS 272, OpcUaClient 138, Galaxy 313)
and the 6 driver IntegrationTests projects compile. Enum-serialization round-trip coverage preserved.
V3Initialapplies clean to a scratch DB;SchemaComplianceTests8/8.V3Initialapplied → both central nodes reached AkkaUp → a seeded Modbus driver spawned and connected to the
10.100.0.35:5020fixture(
DriverInstance MAIN-modbus: connected), deployment sealed (acks=2); AdminUI HTTP 200 on/,/clusters,/unswith 0 unhandled exceptions.opc.tcp://localhost:4840: the rawtag
HR200and the driver have 0 occurrences; the custom namespacens=2holds only the emptyOtOpcUaroot — no authored variables materialize (Batch 4 lights them up).AdminUI Batch-1 stubs (Batch 2/3 replace)
Namespace pages (
NamespaceEdit,ClusterNamespaces) → banners; the 8 driver pages' pre-declared Tagseditors + Namespace bindings removed;
DriverIdentitySectionNamespace dropdown removed;UnsTreeService.CreateTagAsync/UpdateTagAsyncreturn the "moved to /raw" failure; per-equipment taglists empty.
Deferred (owned by later batches, not regressions)
/rawproject tree + reference-picker UI, dualns=Raw/ns=UNSnamespaces + tag-variablematerialization,
{{equip}}reference-relative resolution — Batches 2–4 (address space intentionallydark now).
Configuration.mdv3 note (they describe the not-yet-built UI/addressspace) — updated as Batches 2–4 land.
AbCip_Green_AgainstSimfixture-probe failure(AB CIP sim handshake — environment/fixture gate, untouched).
- Rename AbLegacyEquipmentTagParser → AbLegacyTagDefinitionFactory; TryParse → FromTagConfig(tagConfig, rawPath, out def). Drop the leading-{ heuristic and the deviceHostAddress key; def.Name = rawPath. Add inverse ToTagConfig. Keep strict-enum guards + Inspect. - Options: Tags(IReadOnlyList<AbLegacyTagDefinition>) → RawTags(IReadOnlyList<RawTagEntry>). - Driver: _tagsByRawPath (Ordinal); byName-only resolver; build table from _options.RawTags via FromTagConfig, threading WriteIdempotent + resolving RawTagEntry.DeviceName to the owning device host (ResolveDeviceHost; TODO(v3 WaveC) for the live Device-row host). DiscoverAsync + family-profile validation iterate the built table. Probe picks its address from RawTags via the mapper. - Factory: retire the pre-declared tag DTO path; bind List<RawTagEntry>? RawTags; keep Devices. - Cli BuildOptions: deliver typed defs as RawTagEntry via ToTagConfig. - Tests: AbLegacyRawTags helper; migrate Tags= → RawTags; parser tests → FromTagConfig; equipment-ref driver/ResolveHost tests re-authored as RawTagEntry + read by RawPath. Driver+Contracts+Cli green; 213 driver + 36 Cli tests pass.Rename S7EquipmentTagParser -> S7TagDefinitionFactory; TryParse(reference) -> FromTagConfig(tagConfig, rawPath, out def) (drops the leading-{ heuristic, keys the def by RawPath, adds inverse ToTagConfig). Replace S7DriverOptions.Tags (pre-declared S7TagDefinition list) with RawTags (IReadOnlyList<RawTagEntry>); the driver builds its RawPath->def table from RawTags at Initialize via the factory (skip+log on miss), resolver is byName-only, DiscoverAsync + init guards + address pre-parse now run off that table. Factory retires the pre-declared DTO tag path (RawTags binding only). Cli BuildOptions serialises typed defs to RawTagEntry. Tests migrated to a S7RawTags helper + FromTagConfig; parser test files renamed. Coordinator's EquipmentTagConfigInspector S7 rename left to Wave-B coordinator; Driver.S7.IntegrationTests (Docker-gated) left red per scope. Driver.S7 + Cli build green; Driver.S7.Tests 264/264, S7.Cli.Tests 49/49.- Rename TwinCATEquipmentTagParser -> TwinCATTagDefinitionFactory; TryParse -> FromTagConfig(tagConfig, rawPath, out def). Drop leading-'{' heuristic + the deviceHostAddress key; def.Name = rawPath. Keep strict-enum + Inspect; add a Structure guard (sole tag path now) + inverse ToTagConfig. - Options: Tags -> RawTags (RawTagEntry); keep Devices + protocol fields. - Driver: _tagsByRawPath (Ordinal); byName-only resolver; build table from _options.RawTags via FromTagConfig, threading WriteIdempotent + DeviceName. Multi-device: ResolveDeviceHost matches entry.DeviceName against options.Devices (by name, then host) -> def.DeviceHostAddress (TODO(v3 WaveC): live host from the Device row's DeviceConfig). DiscoverAsync now emits from the table. - Factory: retire pre-declared tags[] DTO path; bind rawTags; keep Devices. - Cli: BuildOptions serialises typed defs -> RawTagEntry via ToTagConfig. - Tests: TwinCATRawTags helper; migrate Tags= -> RawTags=; rename parser tests to FromTagConfig; equipment/resolve-host/array tests delivered via RawTags. Gate: Contracts + Driver build green; Driver.TwinCAT.Tests 192 pass, Cli.Tests 70 pass.Apply the reviewed Modbus exemplar to the AbCip multi-device driver: - Mapper AbCipEquipmentTagParser -> AbCipTagDefinitionFactory; TryParse -> FromTagConfig(tagConfig, rawPath, out def); drop leading-{ heuristic + the deviceHostAddress key; add inverse ToTagConfig; round-trip arrays/members/safety for coverage. - Options: Tags (typed) -> RawTags (RawTagEntry list); Devices list kept. - Driver: byName-only resolver over _tagsByRawPath (Ordinal); table built from RawTags via FromTagConfig, threading entry.DeviceName (device routing key) + entry.WriteIdempotent onto the def. Multi-device routing keys on DeviceName-or-host via a _devicesByRoutingKey index; discovery groups _declaredTags by RoutesToDevice. - Factory: retire the pre-declared Tags DTO/BuildTag; bind List<RawTagEntry> RawTags. - Probe: derive probe tag path from RawTags via the mapper. - Cli BuildOptions projects typed tags -> RawTagEntry (ToTagConfig). - Tests: AbCipRawTags helper (typed def -> RawTagEntry); parser tests -> FromTagConfig; blob-fallback driver tests rewritten to author via RawTags + read by RawPath. Driver.AbCip.Tests 342/342, Cli.Tests 42/42. TODO(v3 WaveC): DeviceName becomes a pure logical name once the device connection host moves to the Device row's DeviceConfig.- Rename FocasEquipmentTagParser -> FocasTagDefinitionFactory; TryParse -> FromTagConfig(tagConfig, rawPath, out def). Drop leading-{ heuristic + the deviceHostAddress blob key; def.Name = rawPath. Add inverse ToTagConfig. writable: absent -> read-only, explicit true honoured (note preserved). - FocasDriverOptions.Tags -> RawTags (IReadOnlyList<RawTagEntry>). - FocasDriver: _tagsByRawPath (Ordinal); byName-only resolver; build table from RawTags via FromTagConfig; multi-device routing threads RawTagEntry.DeviceName -> ResolveDeviceHost match against options.Devices -> def.DeviceHostAddress (TODO v3 WaveC: live host from Device row DeviceConfig). Tolerant per-tag skip+log (mapper/address/matrix miss -> BadNodeIdUnknown); unknown device fails init fast. - Factory: bind List<RawTagEntry> RawTags; retire FocasTagDto/ParseDataType. - CLI FocasCommandBase.BuildOptions -> RawTags via ToTagConfig + DeviceName. - Tests: FocasRawTags helper; migrate Tags= -> RawTags=; rename parser tests to FromTagConfig; rewrite retired blob-ref tests (equipment-tag/capability-gate/ resolve-host) onto authored RawPath. 272 driver + 52 CLI green.Namespace entity + NamespaceKind retired; DriverInstance dropped NamespaceId; Tag is raw-only (no EquipmentId/DriverInstanceId/FullName/FolderPath); driver options replaced pre-declared Tags with RawTags. Stub the retired surfaces so the AdminUI + Host build green; real Raw/UNS authoring arrives in v3 Batch 2/3. - ClusterNamespaces/NamespaceEdit: bodies replaced with 'Namespaces retired' banner - DriverIdentitySection: dropped Namespace dropdown + NamespaceId model field - 8 driver pages: dropped Namespace binding; retired the pre-declared Tags editor (replaced with a '/raw Batch 2' note); multi-device pages keep their Devices editor - UnsTreeService: equipment-tag counts/list -> empty; Create/UpdateTag -> failure result ('Tag authoring moved to the Raw tree (/raw) in v3 Batch 2'); Namespace/ driver-binding queries removed - IScriptTagCatalog: project surviving Tag columns only (path from TagConfig FullName, else Name) - OpcUaClientTagConfigEditor: rebind stale FullName -> model's NodeId - ClusterDrivers: drop retired NamespaceId column AdminUI + Host build 0/0. AdminUI.Tests / Host.IntegrationTests remain red (WP6).Migrate the server composer/applier + 2-node harness tests off the retired Namespace/NamespaceKind + equipment→driver/device binding schema onto the v3 greenfield shape (raw-only Tag; DriverInstance-RawFolderId; equipment via UnsLine). OpcUaServer.Tests (134 build errors -> green, 335 pass / 4 skip): - LIVE (migrated, passing): VirtualTag-historize + ScriptedAlarm composition, composer purity/hierarchy, applier MaterialiseHierarchy + MaterialiseEquipmentTags idempotency (hand-fed applier, not dark). - SKIP dark-until-Batch-4: {{equip}} token expansion, applier equipment-namespace E2E (equipment-tag variable materialization). - RETIRE-with-comment / skip: alias-tag (Tag.EquipmentId binding gone) + device-host equipment binding (architecturally removed). - Add local DarkAddressSpaceReasons Skip-reason constants (mirrors Runtime.Tests). Host.IntegrationTests (green, in-memory): drop deleted-Namespace seeds from MultiClusterScoping + DriverReconnect; reshape EquipmentNamespaceMaterialization to assert the DARK contract (folder nodes decode, EquipmentTags empty despite a seeded v3 raw tag) with the full raw-tag->EquipmentTag materialization kept as a Batch-4-pending Skip. Tests-only; no src/ changes.