Skip to content

Rust resolver returns NO_SEGMENT_MATCH when materialization has no variant for rule #297

@nicklasl

Description

@nicklasl

Problem

When a materialization record exists for the unit but has no variant for the matched rule (empty ruleToVariant), the Rust resolver sets materialization_matched = Some(false) and falls back to segment matching. If the segment targeting doesn't match (e.g. wrong country), the result is NO_SEGMENT_MATCH. Java falls through to hash-based bucket assignment and returns MATCH.

Rust behavior

confidence-resolver/src/lib.rs ~L1072-1128:

match materialization_context.has_rule_materialization(
    read_materialization, &unit, &rule.name,
)? {
    Some(false) => {
        materialization_matched = Some(false);  // <-- blocks fallthrough
        if read_mode.materialization_must_match {
            continue;
        }
    }
    Some(true) => {
        // ... try to select assignment from materialization
    }
    None => { ... }
}

if materialization_matched != Some(true) {
    // falls back to segment_match, which may fail
    materialization_matched = match self.segment_match(...) { ... };
}

When has_rule_materialization returns Some(false) (unit is in materialization but no variant for this rule), Rust sets materialization_matched = Some(false). This forces segment matching. If the segment targeting fails (e.g. country mismatch), the rule is skipped → NO_SEGMENT_MATCH.

Java behavior

AccountResolver.java ~L287-369:

} else {
    // Unit IS in materialization
    // ... targeting check ...
    if (materializationMatched) {
        final Optional<String> variant = materializationInfo.getVariantForRule(rule.getName());
        if (variant.isPresent()) {
            // Use materialization variant
            return variantMatch(...);
        }
        // variant is empty → falls through
    }
}

// Falls through to segment matching + bucket assignment
if (!materializationMatched && !segmentMatches(...)) {
    continue;
}

// Hash-based bucket assignment
final long bucket = Randomizer.getBucket(unit.get(), variantSalt, bucketCount);
matchedAssignmentOpt = spec.getAssignmentsList().stream()
    .filter(variant -> Randomizer.coversBucket(variant, bucket))
    .findFirst();

When getVariantForRule() returns empty, Java does not set materializationMatched = false. It falls through to the hash-based bucket assignment, which uses the targeting key to compute a bucket and pick a variant. The !materializationMatched check passes because materializationMatched was set to true during the targeting check above, so segment matching is skipped entirely.

Affected spec tests

  • mat_matched_no_variant_for_rule

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions