spotify / spotify/confidence-resolver

Rust resolver returns NO_SEGMENT_MATCH when materialization has no variant for rule

Open
#297 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
16
Forks
14
Avg merge
2d 7m
Merged PRs (30d)
40

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in confidence-resolver/src/lib.rs around lines 1072-1128 and compare the Rust flow with AccountResolver.java around lines 287-369. Run the affected spec test mat_matched_no_variant_for_rule and inspect the materialization and bucket-assignment tests. Done means the empty rule variant follows the documented Java behavior and the affected test passes without regressing segment matching.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, rust
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.