fix(template-engine): repeated composition no longer drops nested members — cycle guard keyed on recursion path, not global template id
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -262,7 +262,7 @@ public class FlatteningService
|
||||
IReadOnlyDictionary<int, IReadOnlyList<TemplateComposition>> compositionMap,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<Template>> composedTemplateChains,
|
||||
Dictionary<string, ResolvedAttribute> attributes,
|
||||
HashSet<int> visited)
|
||||
HashSet<int> path)
|
||||
{
|
||||
if (!composedTemplateChains.TryGetValue(composition.ComposedTemplateId, out var composedChain))
|
||||
return;
|
||||
@@ -283,17 +283,27 @@ public class FlatteningService
|
||||
}
|
||||
|
||||
// Descend into nested compositions of every template in the chain.
|
||||
// Guard on the recursion PATH, not globally: composition is by-slot, so a
|
||||
// template legitimately appears twice under different prefixes; only a
|
||||
// template already on the CURRENT path is a cycle.
|
||||
foreach (var composedTemplate in composedChain)
|
||||
{
|
||||
if (!visited.Add(composedTemplate.Id))
|
||||
continue;
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
if (!path.Add(composedTemplate.Id))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
continue;
|
||||
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedAttributesRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, attributes, visited);
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedAttributesRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, attributes, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
path.Remove(composedTemplate.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +635,7 @@ public class FlatteningService
|
||||
IReadOnlyDictionary<int, IReadOnlyList<Template>> composedTemplateChains,
|
||||
Dictionary<string, ResolvedAlarm> alarms,
|
||||
Dictionary<string, int> alarmScriptIds,
|
||||
HashSet<int> visited)
|
||||
HashSet<int> path)
|
||||
{
|
||||
if (!composedTemplateChains.TryGetValue(composition.ComposedTemplateId, out var composedChain))
|
||||
return;
|
||||
@@ -646,17 +656,27 @@ public class FlatteningService
|
||||
}
|
||||
|
||||
// Descend into nested compositions of every template in the chain.
|
||||
// Guard on the recursion PATH, not globally: composition is by-slot, so a
|
||||
// template legitimately appears twice under different prefixes; only a
|
||||
// template already on the CURRENT path is a cycle.
|
||||
foreach (var composedTemplate in composedChain)
|
||||
{
|
||||
if (!visited.Add(composedTemplate.Id))
|
||||
continue;
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
if (!path.Add(composedTemplate.Id))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
continue;
|
||||
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedAlarmsRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, alarms, alarmScriptIds, visited);
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedAlarmsRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, alarms, alarmScriptIds, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
path.Remove(composedTemplate.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,7 +759,7 @@ public class FlatteningService
|
||||
IReadOnlyDictionary<int, IReadOnlyList<TemplateComposition>> compositionMap,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<Template>> composedTemplateChains,
|
||||
Dictionary<string, ResolvedNativeAlarmSource> sources,
|
||||
HashSet<int> visited)
|
||||
HashSet<int> path)
|
||||
{
|
||||
if (!composedTemplateChains.TryGetValue(composition.ComposedTemplateId, out var composedChain))
|
||||
return;
|
||||
@@ -758,17 +778,27 @@ public class FlatteningService
|
||||
}
|
||||
}
|
||||
|
||||
// Guard on the recursion PATH, not globally: composition is by-slot, so a
|
||||
// template legitimately appears twice under different prefixes; only a
|
||||
// template already on the CURRENT path is a cycle.
|
||||
foreach (var composedTemplate in composedChain)
|
||||
{
|
||||
if (!visited.Add(composedTemplate.Id))
|
||||
continue;
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
if (!path.Add(composedTemplate.Id))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
continue;
|
||||
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedNativeAlarmSourcesRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, sources, visited);
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedNativeAlarmSourcesRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
compositionMap, composedTemplateChains, sources, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
path.Remove(composedTemplate.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,7 +919,7 @@ public class FlatteningService
|
||||
IReadOnlyDictionary<int, IReadOnlyList<Template>> composedTemplateChains,
|
||||
Dictionary<string, ResolvedScript> scripts,
|
||||
Dictionary<int, string> scriptCanonicalById,
|
||||
HashSet<int> visited)
|
||||
HashSet<int> path)
|
||||
{
|
||||
if (!composedTemplateChains.TryGetValue(composition.ComposedTemplateId, out var composedChain))
|
||||
return;
|
||||
@@ -910,17 +940,27 @@ public class FlatteningService
|
||||
}
|
||||
|
||||
// Descend into nested compositions of every template in the chain.
|
||||
// Guard on the recursion PATH, not globally: composition is by-slot, so a
|
||||
// template legitimately appears twice under different prefixes; only a
|
||||
// template already on the CURRENT path is a cycle.
|
||||
foreach (var composedTemplate in composedChain)
|
||||
{
|
||||
if (!visited.Add(composedTemplate.Id))
|
||||
continue;
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
if (!path.Add(composedTemplate.Id))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
if (!compositionMap.TryGetValue(composedTemplate.Id, out var nestedCompositions))
|
||||
continue;
|
||||
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedScriptsRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}", parentPath: prefix,
|
||||
compositionMap, composedTemplateChains, scripts, scriptCanonicalById, visited);
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedScriptsRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}", parentPath: prefix,
|
||||
compositionMap, composedTemplateChains, scripts, scriptCanonicalById, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
path.Remove(composedTemplate.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user