///|
/// A scenario is a deterministic set of contexts and typed requests. It makes
/// it possible to replay release decisions in CI, compare two configurations,
/// and publish benchmark evidence without connecting to a remote service.
pub(all) struct EvaluationScenario {
name : String
requests : Array[EvaluationRequest]
contexts : Array[EvalContext]
} derive(Debug)
///|
pub fn scenario(
name : String,
requests : Array[EvaluationRequest],
contexts : Array[EvalContext],
) -> EvaluationScenario {
{ name, requests, contexts }
}
///|
pub(all) struct ScenarioReport {
name : String
contexts : Int
requests_per_context : Int
total : Int
matches : Int
defaults : Int
disabled : Int
target_misses : Int
rollout_misses : Int
type_mismatches : Int
ledger : DecisionLedger
} derive(Debug)
///|
pub fn[P : FeatureProvider] EvaluationScenario::run(
self : EvaluationScenario,
provider : P,
) -> ScenarioReport {
let ledger = new_ledger()
for ctx in self.contexts {
let _ = evaluate_with_ledger(provider, self.requests, ctx, ledger)
}
{
name: self.name,
contexts: self.contexts.length(),
requests_per_context: self.requests.length(),
total: ledger.total,
matches: ledger.matches,
defaults: ledger.defaults,
disabled: ledger.disabled,
target_misses: ledger.target_misses,
rollout_misses: ledger.rollout_misses,
type_mismatches: ledger.type_mismatches,
ledger,
}
}
///|
pub fn ScenarioReport::success_ratio(self : ScenarioReport) -> Double {
self.ledger.success_ratio()
}
///|
pub fn ScenarioReport::summary(self : ScenarioReport) -> String {
self.name +
": contexts=" +
self.contexts.to_string() +
", requests=" +
self.total.to_string() +
", matches=" +
self.matches.to_string() +
", defaults=" +
self.defaults.to_string() +
", ratio=" +
self.success_ratio().to_string()
}
///|
pub fn ScenarioReport::is_reproducible(self : ScenarioReport) -> Bool {
self.total == self.contexts * self.requests_per_context
}
///|
pub fn acceptance_scenario() -> EvaluationScenario {
let cases = benchmark_cases()
let requests = cases.map(item => request(item.key, item.default_value))
let contexts = [
context("acceptance-prod-pro").with_attrs([
("environment", string_value("production")),
("region", string_value("cn-east")),
("plan", string_value("pro")),
("platform", string_value("mobile")),
]),
context("acceptance-staging-free").with_attrs([
("environment", string_value("staging")),
("region", string_value("cn-north")),
("plan", string_value("free")),
("platform", string_value("web")),
]),
context("acceptance-prod-free").with_attrs([
("environment", string_value("production")),
("region", string_value("cn-east")),
("plan", string_value("free")),
("platform", string_value("web")),
]),
context("acceptance-staging-pro").with_attrs([
("environment", string_value("staging")),
("region", string_value("cn-north")),
("plan", string_value("pro")),
("platform", string_value("mobile")),
]),
]
scenario("OSC2026 acceptance matrix", requests, contexts)
}
///|
pub fn run_acceptance_scenario() -> ScenarioReport {
acceptance_scenario().run(
benchmark_cases().fold(init=empty_provider(), (provider, item) => {
match (item.rollout_percentage, item.target_attr, item.target_value) {
(Some(percentage), _, _) =>
provider.with_value_rollout(item.key, item.value, percentage~)
(_, Some(attr), Some(expected)) =>
provider.with_value_target(
item.key,
item.value,
attr~,
equals=expected,
)
_ => provider.with_value(item.key, item.value)
}
}),
)
}