contracts(b2-waveA): RawNode view-model + IRawTreeService read surface + RawRenameResult

Wave A contract-first fan-out types for the /raw project tree:
- RawNode/RawNodeKind: shared tree view-model (Enterprise→Cluster→Folder→Driver→Device→TagGroup→Tag), lazy-load metadata, per-kind ids + DriverType propagation for menu gating.
- IRawTreeService: committed READ surface (LoadRootsAsync + LoadChildrenAsync); WP1 extends with the mutation surface, WP2 consumes read-only.
- RawRenameResult: rename outcome carrying non-blocking warnings (historized/UNS-referenced now; script-scan in Batch 3).

Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
Joseph Doherty
2026-07-16 02:20:05 -04:00
parent c3277b52c9
commit fbf3c26c2a
3 changed files with 169 additions and 0 deletions
@@ -0,0 +1,44 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>
/// Loads and mutates the v3 Raw project tree (<c>/raw</c>) — the cluster-rooted
/// Enterprise → Cluster → Folder → Driver → Device → TagGroup → Tag hierarchy — from the config
/// database. The peer of <see cref="IUnsTreeService"/> for the Raw subtree.
/// <para>
/// <b>Contract split (Batch 2 Wave A):</b> this file's committed surface is the <b>read</b> contract
/// the <c>RawTree</c> component consumes — <see cref="LoadRootsAsync"/> (eager Enterprise→Cluster roots)
/// and <see cref="LoadChildrenAsync"/> (one lazy level per expand). B2-WP1 (<c>RawTreeService</c>)
/// <b>extends this interface</b> with the mutation surface — create/rename/delete per node type,
/// move-into-folder, and the per-node edit projections — enforcing the integrity rules with
/// user-readable errors (a delete blocked by a <c>UnsTagReference</c> names the referencing equipment).
/// Rename methods return <see cref="RawRenameResult"/> so the UI can show non-blocking warnings.
/// Mutations reuse <see cref="UnsMutationResult"/>. WP2 must not add to this interface.
/// </para>
/// </summary>
public interface IRawTreeService
{
/// <summary>
/// Loads the eager top of the Raw tree: Enterprise → Cluster roots, each cluster marked
/// <see cref="RawNode.HasLazyChildren"/> so its folders + drivers load on first expand. Empty
/// clusters are retained so they stay visible and authorable. The returned nodes are detached
/// view-models, safe to hold and mutate UI state on after the underlying context is disposed.
/// </summary>
/// <param name="ct">A token to cancel the load.</param>
/// <returns>The enterprise roots, populated down to (but not through) their clusters.</returns>
Task<IReadOnlyList<RawNode>> LoadRootsAsync(CancellationToken ct = default);
/// <summary>
/// Loads the direct children of one container node — the next level down, keyed off
/// <paramref name="parent"/>'s <see cref="RawNode.Kind"/> and <see cref="RawNode.EntityId"/>:
/// a Cluster yields its root Folders + Drivers; a Folder yields sub-Folders + Drivers; a Driver
/// yields its Devices; a Device yields its root TagGroups + Tags; a TagGroup yields sub-TagGroups
/// + Tags. Tags are leaves and yield nothing. Ordering is deterministic and ordinal. Reads
/// untracked; returns detached nodes. Per-device tag counts can be large — the implementation may
/// page, but the committed shape returns the level's nodes (paging knobs, if any, are WP1's to add
/// on the concrete type without changing this signature's meaning for the tree).
/// </summary>
/// <param name="parent">The container node whose direct children to load.</param>
/// <param name="ct">A token to cancel the load.</param>
/// <returns>The parent's direct child nodes; empty for a leaf or an empty container.</returns>
Task<IReadOnlyList<RawNode>> LoadChildrenAsync(RawNode parent, CancellationToken ct = default);
}