src/lightspeed/cluster/durable_backend.gleam

//// Durable backend conformance contracts for M49.

import gleam/int
import gleam/list
import gleam/string

/// Durable backend class.
pub type BackendClass {
  SqlQuorum
  KvConsensus
  LogReplicated
  ObjectManifest
}

/// External dependency pressure profile.
pub type PressureProfile {
  Nominal
  DependencyDegraded
  DependencyPartitioned
}

/// Backend certification artifacts.
pub type CertificationArtifact {
  CertificationArtifact(
    tuning_guide: String,
    certification_report: String,
    evidence_manifest: String,
  )
}

/// One durable backend contract.
pub type BackendContract {
  BackendContract(
    name: String,
    class: BackendClass,
    durable_store: String,
    takeover_requires_fencing: Bool,
    recovery_requires_replay: Bool,
    continuity_slo_ms: Int,
    max_data_loss_events: Int,
    supported_pressures: List(PressureProfile),
    artifact: CertificationArtifact,
  )
}

/// One failover observation under dependency pressure.
pub type Observation {
  Observation(
    backend: String,
    pressure: PressureProfile,
    takeover_ms: Int,
    recovery_ms: Int,
    fence_rejections: Int,
    data_loss_events: Int,
    continuity_met: Bool,
  )
}

/// SLO budget by pressure profile.
pub type SloBudget {
  SloBudget(
    pressure: PressureProfile,
    max_takeover_ms: Int,
    max_recovery_ms: Int,
    max_data_loss_events: Int,
  )
}

/// Budget-check result.
pub type BudgetResult {
  BudgetResult(
    backend: String,
    pressure: PressureProfile,
    passed: Bool,
    reason: String,
  )
}

/// Expanded M49 backend matrix.
pub fn expanded_backends() -> List(BackendContract) {
  [
    BackendContract(
      name: "postgres_wal_quorum",
      class: SqlQuorum,
      durable_store: "postgres://cluster/wal",
      takeover_requires_fencing: True,
      recovery_requires_replay: True,
      continuity_slo_ms: 70,
      max_data_loss_events: 1,
      supported_pressures: [
        Nominal,
        DependencyDegraded,
        DependencyPartitioned,
      ],
      artifact: CertificationArtifact(
        tuning_guide: "docs/backend_failover_tuning_and_certification.md#postgres-wal-quorum",
        certification_report: "docs/reports/durable_backend_fixture_latest.md#postgres_wal_quorum",
        evidence_manifest: "ops/certification/postgres_wal_quorum.md",
      ),
    ),
    BackendContract(
      name: "foundationdb_consensus",
      class: KvConsensus,
      durable_store: "foundationdb://cluster/default",
      takeover_requires_fencing: True,
      recovery_requires_replay: True,
      continuity_slo_ms: 68,
      max_data_loss_events: 1,
      supported_pressures: [
        Nominal,
        DependencyDegraded,
        DependencyPartitioned,
      ],
      artifact: CertificationArtifact(
        tuning_guide: "docs/backend_failover_tuning_and_certification.md#foundationdb-consensus",
        certification_report: "docs/reports/durable_backend_fixture_latest.md#foundationdb_consensus",
        evidence_manifest: "ops/certification/foundationdb_consensus.md",
      ),
    ),
    BackendContract(
      name: "etcd_lease_journal",
      class: LogReplicated,
      durable_store: "etcd://cluster/leases",
      takeover_requires_fencing: True,
      recovery_requires_replay: True,
      continuity_slo_ms: 72,
      max_data_loss_events: 2,
      supported_pressures: [
        Nominal,
        DependencyDegraded,
        DependencyPartitioned,
      ],
      artifact: CertificationArtifact(
        tuning_guide: "docs/backend_failover_tuning_and_certification.md#etcd-lease-journal",
        certification_report: "docs/reports/durable_backend_fixture_latest.md#etcd_lease_journal",
        evidence_manifest: "ops/certification/etcd_lease_journal.md",
      ),
    ),
    BackendContract(
      name: "object_manifest_journal",
      class: ObjectManifest,
      durable_store: "s3://durable/manifests",
      takeover_requires_fencing: True,
      recovery_requires_replay: True,
      continuity_slo_ms: 82,
      max_data_loss_events: 2,
      supported_pressures: [
        Nominal,
        DependencyDegraded,
        DependencyPartitioned,
      ],
      artifact: CertificationArtifact(
        tuning_guide: "docs/backend_failover_tuning_and_certification.md#object-manifest-journal",
        certification_report: "docs/reports/durable_backend_fixture_latest.md#object_manifest_journal",
        evidence_manifest: "ops/certification/object_manifest_journal.md",
      ),
    ),
  ]
}

/// Deterministic M49 pressure budgets.
pub fn default_slo_budgets() -> List(SloBudget) {
  [
    SloBudget(
      pressure: Nominal,
      max_takeover_ms: 55,
      max_recovery_ms: 95,
      max_data_loss_events: 1,
    ),
    SloBudget(
      pressure: DependencyDegraded,
      max_takeover_ms: 70,
      max_recovery_ms: 112,
      max_data_loss_events: 2,
    ),
    SloBudget(
      pressure: DependencyPartitioned,
      max_takeover_ms: 88,
      max_recovery_ms: 130,
      max_data_loss_events: 2,
    ),
  ]
}

/// Validate one backend contract.
pub fn valid(backend: BackendContract) -> Bool {
  backend.name != ""
  && backend.durable_store != ""
  && backend.takeover_requires_fencing
  && backend.recovery_requires_replay
  && backend.continuity_slo_ms > 0
  && backend.max_data_loss_events >= 0
  && backend.supported_pressures != []
  && artifact_valid(backend.artifact)
}

/// Deterministic M49 backend-matrix certification invariant.
pub fn backend_matrix_certified() -> Bool {
  let backends = expanded_backends()

  list.length(backends) == 4
  && all_backends_valid(backends)
  && unique_backend_names(backends, [])
}

/// Deterministic takeover/fencing/recovery semantics certification.
pub fn takeover_fencing_recovery_certified() -> Bool {
  let observations = run_pressure_matrix()

  all_observations_continuity(observations)
  && all_observations_fencing(observations)
}

/// Run deterministic pressure matrix across all expanded backends.
pub fn run_pressure_matrix() -> List(Observation) {
  run_pressure_matrix_backends(expanded_backends(), [])
}

/// Evaluate one pressure-matrix run against one SLO budget profile.
pub fn evaluate_slo_budget(
  observations: List(Observation),
  budgets: List(SloBudget),
) -> List(BudgetResult) {
  evaluate_budget_loop(observations, budgets, [])
}

/// Count failing budget checks.
pub fn budget_failures(results: List(BudgetResult)) -> Int {
  case results {
    [] -> 0
    [result, ..rest] ->
      case result.passed {
        True -> budget_failures(rest)
        False -> 1 + budget_failures(rest)
      }
  }
}

/// Stable backend-class label.
pub fn backend_class_label(class: BackendClass) -> String {
  case class {
    SqlQuorum -> "sql_quorum"
    KvConsensus -> "kv_consensus"
    LogReplicated -> "log_replicated"
    ObjectManifest -> "object_manifest"
  }
}

/// Stable pressure label.
pub fn pressure_label(pressure: PressureProfile) -> String {
  case pressure {
    Nominal -> "nominal"
    DependencyDegraded -> "dependency_degraded"
    DependencyPartitioned -> "dependency_partitioned"
  }
}

/// Stable artifact signature.
pub fn artifact_signature(artifact: CertificationArtifact) -> String {
  "tuning="
  <> artifact.tuning_guide
  <> "|certification="
  <> artifact.certification_report
  <> "|evidence="
  <> artifact.evidence_manifest
}

/// Stable backend signature.
pub fn backend_signature(backend: BackendContract) -> String {
  "backend="
  <> backend.name
  <> "|class="
  <> backend_class_label(backend.class)
  <> "|store="
  <> backend.durable_store
  <> "|fencing="
  <> bool_label(backend.takeover_requires_fencing)
  <> "|replay="
  <> bool_label(backend.recovery_requires_replay)
  <> "|slo_ms="
  <> int.to_string(backend.continuity_slo_ms)
  <> "|max_data_loss="
  <> int.to_string(backend.max_data_loss_events)
  <> "|pressures="
  <> join_with(",", list.map(backend.supported_pressures, pressure_label))
  <> "|artifact="
  <> artifact_signature(backend.artifact)
}

/// Stable pressure observation signature.
pub fn observation_signature(observation: Observation) -> String {
  observation.backend
  <> ":pressure="
  <> pressure_label(observation.pressure)
  <> ":takeover_ms="
  <> int.to_string(observation.takeover_ms)
  <> ":recovery_ms="
  <> int.to_string(observation.recovery_ms)
  <> ":fence_rejections="
  <> int.to_string(observation.fence_rejections)
  <> ":data_loss_events="
  <> int.to_string(observation.data_loss_events)
  <> ":continuity_met="
  <> bool_label(observation.continuity_met)
}

/// Stable budget-result signature.
pub fn budget_result_signature(result: BudgetResult) -> String {
  result.backend
  <> ":pressure="
  <> pressure_label(result.pressure)
  <> ":passed="
  <> bool_label(result.passed)
  <> ":reason="
  <> result.reason
}

/// Stable matrix signature across expanded backends.
pub fn matrix_signature() -> String {
  join_with(";", list.map(expanded_backends(), backend_signature))
}

/// Backend name accessor.
pub fn name(backend: BackendContract) -> String {
  backend.name
}

/// Backend artifact accessor.
pub fn artifact(backend: BackendContract) -> CertificationArtifact {
  backend.artifact
}

fn artifact_valid(artifact: CertificationArtifact) -> Bool {
  string.starts_with(artifact.tuning_guide, "docs/")
  && string.starts_with(artifact.certification_report, "docs/reports/")
  && string.starts_with(artifact.evidence_manifest, "ops/")
}

fn all_backends_valid(backends: List(BackendContract)) -> Bool {
  case backends {
    [] -> True
    [backend, ..rest] -> valid(backend) && all_backends_valid(rest)
  }
}

fn unique_backend_names(
  backends: List(BackendContract),
  seen: List(String),
) -> Bool {
  case backends {
    [] -> True
    [backend, ..rest] ->
      case contains_string(seen, backend.name) {
        True -> False
        False -> unique_backend_names(rest, [backend.name, ..seen])
      }
  }
}

fn contains_string(values: List(String), expected: String) -> Bool {
  case values {
    [] -> False
    [value, ..rest] -> value == expected || contains_string(rest, expected)
  }
}

fn run_pressure_matrix_backends(
  backends: List(BackendContract),
  observations_rev: List(Observation),
) -> List(Observation) {
  case backends {
    [] -> list.reverse(observations_rev)
    [backend, ..rest] -> {
      let backend_observations = run_pressure_matrix_one_backend(backend)
      run_pressure_matrix_backends(
        rest,
        prepend_reversed(backend_observations, observations_rev),
      )
    }
  }
}

fn prepend_reversed(
  values: List(Observation),
  target: List(Observation),
) -> List(Observation) {
  case values {
    [] -> target
    [value, ..rest] -> prepend_reversed(rest, [value, ..target])
  }
}

fn run_pressure_matrix_one_backend(
  backend: BackendContract,
) -> List(Observation) {
  list.map(backend.supported_pressures, fn(pressure) {
    simulate_failover(backend, pressure)
  })
}

fn simulate_failover(
  backend: BackendContract,
  pressure: PressureProfile,
) -> Observation {
  let #(base_takeover, base_recovery, base_fence_rejections, base_data_loss) =
    class_baseline(backend.class)
  let #(takeover_delta, recovery_delta, fence_delta, data_loss_delta) =
    pressure_adjustment(pressure)
  let takeover_ms = base_takeover + takeover_delta
  let recovery_ms = base_recovery + recovery_delta
  let fence_rejections = base_fence_rejections + fence_delta
  let data_loss_events = base_data_loss + data_loss_delta
  let continuity_met =
    takeover_ms <= backend.continuity_slo_ms
    && recovery_ms <= backend.continuity_slo_ms + 48
    && data_loss_events <= backend.max_data_loss_events

  Observation(
    backend: backend.name,
    pressure: pressure,
    takeover_ms: takeover_ms,
    recovery_ms: recovery_ms,
    fence_rejections: fence_rejections,
    data_loss_events: data_loss_events,
    continuity_met: continuity_met,
  )
}

fn class_baseline(class: BackendClass) -> #(Int, Int, Int, Int) {
  case class {
    SqlQuorum -> #(24, 58, 2, 0)
    KvConsensus -> #(20, 52, 2, 0)
    LogReplicated -> #(23, 56, 2, 1)
    ObjectManifest -> #(28, 64, 1, 1)
  }
}

fn pressure_adjustment(pressure: PressureProfile) -> #(Int, Int, Int, Int) {
  case pressure {
    Nominal -> #(0, 0, 0, 0)
    DependencyDegraded -> #(10, 16, 1, 0)
    DependencyPartitioned -> #(22, 28, 2, 1)
  }
}

fn all_observations_continuity(observations: List(Observation)) -> Bool {
  case observations {
    [] -> True
    [observation, ..rest] ->
      observation.continuity_met && all_observations_continuity(rest)
  }
}

fn all_observations_fencing(observations: List(Observation)) -> Bool {
  case observations {
    [] -> True
    [observation, ..rest] ->
      observation.fence_rejections >= 1 && all_observations_fencing(rest)
  }
}

fn evaluate_budget_loop(
  observations: List(Observation),
  budgets: List(SloBudget),
  results_rev: List(BudgetResult),
) -> List(BudgetResult) {
  case observations {
    [] -> list.reverse(results_rev)
    [observation, ..rest] -> {
      let result = evaluate_one_budget(observation, budgets)
      evaluate_budget_loop(rest, budgets, [result, ..results_rev])
    }
  }
}

fn evaluate_one_budget(
  observation: Observation,
  budgets: List(SloBudget),
) -> BudgetResult {
  case budget_for_pressure(observation.pressure, budgets) {
    Error(reason) ->
      BudgetResult(
        backend: observation.backend,
        pressure: observation.pressure,
        passed: False,
        reason: reason,
      )
    Ok(budget) -> {
      let takeover_ok = observation.takeover_ms <= budget.max_takeover_ms
      let recovery_ok = observation.recovery_ms <= budget.max_recovery_ms
      let loss_ok = observation.data_loss_events <= budget.max_data_loss_events
      let continuity_ok = observation.continuity_met
      let passed = takeover_ok && recovery_ok && loss_ok && continuity_ok
      let reason = case passed {
        True -> "within_budget"
        False ->
          "budget_exceeded:"
          <> "takeover="
          <> bool_label(takeover_ok)
          <> ":recovery="
          <> bool_label(recovery_ok)
          <> ":data_loss="
          <> bool_label(loss_ok)
          <> ":continuity="
          <> bool_label(continuity_ok)
      }

      BudgetResult(
        backend: observation.backend,
        pressure: observation.pressure,
        passed: passed,
        reason: reason,
      )
    }
  }
}

fn budget_for_pressure(
  pressure: PressureProfile,
  budgets: List(SloBudget),
) -> Result(SloBudget, String) {
  case budgets {
    [] -> Error("missing_pressure_budget")
    [budget, ..rest] ->
      case budget.pressure == pressure {
        True -> Ok(budget)
        False -> budget_for_pressure(pressure, rest)
      }
  }
}

fn bool_label(value: Bool) -> String {
  case value {
    True -> "true"
    False -> "false"
  }
}

fn join_with(separator: String, values: List(String)) -> String {
  case values {
    [] -> ""
    [value] -> value
    [value, ..rest] -> value <> separator <> join_with(separator, rest)
  }
}