Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/main/java/blue/language/merge/Merger.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@
/**
* Concrete Blue Language merge engine.
*
* <p>This class is sealed because it issues verified reference-resolution
* evidence. Custom merge behavior must use {@link MergingProcessor}, which is
* the supported extension point.</p>
* <p>Custom merge behavior should use {@link MergingProcessor}, which is the
* supported extension point. The class remains extensible for compatibility
* with existing clients.</p>
*/
public final class Merger implements NodeResolver {
public class Merger implements NodeResolver {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid issuing verified evidence from subclass overrides

When an existing compatibility subclass overrides resolve or merge and then uses the inherited resolveSnapshot(..., NO_LIMITS), the snapshot path still calls those virtual methods and mints a VerifiedReferenceResolution for whatever resolved graph the subclass returned. That evidence is accepted by ResolvedReferenceCache.putVerifiedResolved, so a subclass can accidentally publish a resolved graph that was not produced by the trusted verifier under the canonical BlueId; keeping the class extensible needs to disable verified evidence for subclass-produced resolutions or otherwise route verification through non-overridable logic.

Useful? React with 👍 / 👎.


private final MergingProcessor mergingProcessor;
private final NodeProvider nodeProvider;
Expand Down
116 changes: 115 additions & 1 deletion src/main/java/blue/language/snapshot/FrozenNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ public static FrozenNode fromResolvedNode(Node node, ResolvedStructuralInterner
return fromNode(node, false, interner, false);
}

/**
* Freezes a resolved graph using the legacy BlueId-keyed interning contract.
*
* <p>New code should prefer {@link ResolvedReferenceCache#freezeResolved(Node)},
* which interns by exact resolved structure and keeps provider verification
* separate from graph sharing. This overload remains for binary compatibility
* with clients compiled against the 3.0 API.</p>
*/
@Deprecated
public static FrozenNode fromResolvedNode(Node node, ResolvedReferenceInterner interner) {
return fromLegacyResolvedNode(node, interner, false);
}

public static FrozenNode fromUncheckedCanonicalNode(Node node) {
return fromNode(node, true, null, false);
}
Expand Down Expand Up @@ -236,6 +249,83 @@ private static FrozenNode fromNode(Node node,
return frozen;
}

private static FrozenNode fromLegacyResolvedNode(Node node,
ResolvedReferenceInterner interner,
boolean previousAnchorContext) {
Objects.requireNonNull(node, "node");
if (interner != null && node.getBlueId() != null) {
FrozenNode cached = interner.lookup(node.getBlueId());
if (cached != null) {
return cached;
}
}
FrozenNode frozen = builder()
.name(node.getName())
.description(node.getDescription())
.type(node.getType() != null
? fromLegacyResolvedNode(node.getType(), interner, false)
: null)
.itemType(node.getItemType() != null
? fromLegacyResolvedNode(node.getItemType(), interner, false)
: null)
.keyType(node.getKeyType() != null
? fromLegacyResolvedNode(node.getKeyType(), interner, false)
: null)
.valueType(node.getValueType() != null
? fromLegacyResolvedNode(node.getValueType(), interner, false)
: null)
.value(node.getValue())
.items(freezeLegacyResolvedItems(node.getItems(), interner))
.properties(freezeLegacyResolvedProperties(node.getProperties(), interner))
.contracts(node.getContracts() != null
? fromLegacyResolvedNode(node.getContracts(), interner, false)
: null)
.referenceBlueId(node.getBlueId())
.schema(node.getSchema())
.mergePolicy(node.getMergePolicy())
.previousBlueId(node.getPreviousBlueId())
.position(node.getPosition())
.blue(node.getBlue() != null
? fromLegacyResolvedNode(node.getBlue(), interner, false)
: null)
.inlineValue(node.isInlineValue())
.strictCanonical(false)
.strictBlueIdValidation(false)
.previousAnchorContext(previousAnchorContext)
.build();
if (interner != null && node.getBlueId() != null && !node.isReferenceOnly()) {
return interner.intern(node.getBlueId(), frozen);
}
return frozen;
}

private static List<FrozenNode> freezeLegacyResolvedItems(
List<Node> source,
ResolvedReferenceInterner interner) {
if (source == null) {
return null;
}
List<FrozenNode> result = new ArrayList<>(source.size());
for (Node item : source) {
result.add(fromLegacyResolvedNode(item, interner, true));
}
return result;
}

private static Map<String, FrozenNode> freezeLegacyResolvedProperties(
Map<String, Node> source,
ResolvedReferenceInterner interner) {
if (source == null || source.isEmpty()) {
return null;
}
Map<String, FrozenNode> result = new LinkedHashMap<>();
for (Map.Entry<String, Node> entry : source.entrySet()) {
result.put(entry.getKey(),
fromLegacyResolvedNode(entry.getValue(), interner, false));
}
return result;
}

public ResolvedStructuralKey resolvedStructuralKey() {
ResolvedStructuralKey key = resolvedStructuralKey;
if (key == null) {
Expand Down Expand Up @@ -1846,8 +1936,32 @@ FrozenNode build() {
}
}

public interface ResolvedStructuralInterner {
/**
* Legacy BlueId-keyed resolved-reference interner.
*
* @deprecated BlueId-keyed graph interning cannot establish that a
* materialized resolved view is the verified standalone content for that
* BlueId. Use {@link ResolvedReferenceCache} and structural interning.
*/
@Deprecated
public interface ResolvedReferenceInterner {
FrozenNode lookup(String blueId);

FrozenNode intern(String blueId, FrozenNode node);
}

public interface ResolvedStructuralInterner extends ResolvedReferenceInterner {
FrozenNode intern(ResolvedStructuralKey structuralKey, FrozenNode node);

@Override
default FrozenNode lookup(String blueId) {
return null;
}

@Override
default FrozenNode intern(String blueId, FrozenNode node) {
return node;
}
}

/**
Expand Down
Loading
Loading