Skip to content

calculate_sla's duplicate detection ignores the severity symbol: identical configs across severities make different-severity resubmissions idempotent replays #521

Description

@usmanimamu17-create

Problem

The duplicate/replay comparison in calculate_sla (src/lib.rs/src/calculation.rs) checks MTTR and threshold but never the severity:

if prev.config_version_hash == config_version_hash {
    if prev.mttr_minutes != mttr_minutes || prev.threshold_minutes != cfg.threshold_minutes {
        // conflict -> DuplicateOutageInput
    }
    // otherwise: replay — return prev without touching state
    return Ok(prev);
}

The caller's severity argument is not compared against prev's severity (the stored SLAResult doesn't even carry a severity field — it carries threshold_minutes and config_version_hash). Two severities with identical configs (e.g. high and medium both configured to threshold 30 / penalty 50 / reward 750 — allowed by validate_config) produce the same threshold_minutes and the same overall config hash, so resubmitting the same outage under the other severity with the same MTTR is treated as an idempotent replay.

Consequences:

  • A severity change is silently dropped: an operator correcting a submission from high to medium (with equal configs) gets back the high-generated result with no error, no new history entry, and no event — the correction is invisible.
  • The duplicate policy's "conflicting inputs" definition omits severity: SC-W5-046's table keys on (mttr_minutes, threshold_minutes); a different severity with matching threshold is "idempotent" even though the caller's intent changed — the table itself treats severity as irrelevant.
  • The stored entry is indistinguishable: SLAResult has no severity field, so even get_history_by_outage (companion issue) cannot tell which severity produced an entry; the severity lives only in the event's topic[2], which history queries never read.

Root cause

SLAResult predates the config-hash duplicate policy and carries thresholds but not severity; the comparison logic was built on the fields the struct happened to have.

Why this is architecturally hard

  1. Fixing it properly means adding severity to SLAResult (an ABI change — RESULT_SCHEMA_FIELD_COUNT and RESULT_SCHEMA_VERSION must bump, the guardrail updates, history entries change layout — a migration) or comparing severity against the event/history context, which history entries don't store.
  2. The duplicate policy's documented matrix must be updated to include severity in the conflict comparison (or explicitly document severity-blind semantics), and the dup_input event payload should carry the severity context.
  3. The interaction with MAX_RECALCS_PER_OUTAGE and config generations means the fix must decide whether a severity-only change (same config hash) is a conflict, a replay, or a new generation — a policy decision, not just a comparison fix.

Acceptance criteria

  • A severity change for the same outage is either detected as a conflict, recorded as a new generation, or explicitly documented as severity-blind with rationale.
  • The SC-W5-046 matrix and the SLAError::DuplicateOutageInput docs match the implemented behavior.
  • Tests cover the equal-config different-severity resubmission case.

Out of scope

The SLAResult schema migration itself (if severity is added) and the history-generation attribution (companion issue).

Getting started

just test

Good first files to read: apexchainx_calculator/src/lib.rs (calculate_sla duplicate block, SLAResult), apexchainx_calculator/src/calculation.rs (same block).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardStellar WaveIssues in the Stellar wave programThird CampaignCampaign: Third Campaignarea/calculationImported campaign issue labelpriority/highImported campaign issue label

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions