feat(template-engine): contained names for composition-derived templates
A composition-derived template now stores its contained name — the
composition slot's InstanceName (e.g. "Pump"), unique only within its
owner — instead of the dotted global path ("Motor Controller.Pump").
The qualified hierarchical name is computed on read.
- TemplateNaming.QualifiedName: walks the OwnerCompositionId chain to
build the dotted path; null-safe, cycle-guarded.
- TemplateConfiguration: the unique index on Template.Name becomes
filtered (WHERE IsDerived = 0) — base templates stay globally unique;
derived templates' uniqueness is the existing (TemplateId,
InstanceName) index on TemplateComposition.
- Migration ContainedDerivedTemplateNames: rewrites derived rows to the
contained name; Down rebuilds the dotted names via a recursive CTE
before restoring the global index.
- TemplateService: composition create/rename store the contained name;
the dotted-name collision pre-checks and cascade-rename are removed
(a slot rename no longer touches nested derived templates).
- TemplateEdit: title shows the contained name; the qualified path is a
breadcrumb subtitle; "composed inside" uses the owner's qualified name.
TDD: 4 TemplateNaming tests + updated composition tests. TemplateEngine
293, ConfigurationDatabase 114, CentralUI 316 green. Migration applied to
the dev cluster and verified in the browser (Motor Controller.Pump now
titled "Pump"; nested Motor Controller.Pump.TempSensor resolves).
Design: docs/plans/2026-05-18-contained-template-names-design.md
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Contained Names for Composition-Derived Templates — Design
|
||||
|
||||
**Date:** 2026-05-18
|
||||
**Status:** Approved (brainstorming) — implementation to follow
|
||||
**Status:** Implemented
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -81,6 +81,18 @@ When a template composes a feature module, members from that module are addresse
|
||||
- The composing template's own members (not from a module) have no prefix — they are top-level names.
|
||||
- Naming collision detection operates on canonical names, so two modules can define the same member name as long as their module instance names differ.
|
||||
|
||||
### Derived template naming
|
||||
|
||||
A composition slot is materialized as its own *derived* template. A derived
|
||||
template stores a **contained name** — the composition slot's instance name
|
||||
(e.g. `Pump`), unique only within its owner. The **qualified name**
|
||||
(`Motor Controller.Pump`, or `Motor Controller.Pump.TempSensor` when nested) is
|
||||
*computed* on read by walking the owner-composition chain — it is not stored.
|
||||
Only base (user-authored) templates are globally unique by name; a derived
|
||||
template's uniqueness is the slot-name uniqueness within its owner. The Central
|
||||
UI shows the contained name as the template title and the qualified path as a
|
||||
breadcrumb.
|
||||
|
||||
## Override Granularity
|
||||
|
||||
Override and lock rules apply per entity type at the following granularity:
|
||||
|
||||
Reference in New Issue
Block a user