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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions