feat(sim): rutster-sim crate skeleton + Scenario/ScenarioStep types (slice-4½ S1)
The critical-path foundation for the benchmark + simulation harness. Scenario is a TOML-deserializable scripted-caller data type; ScenarioStep covers speak_loud / speak_quiet / pause / await_reply / end. Determinism is the point -- reproducible thresholds in CI (ADR-0010). All other sim modules land as stubs here + fill in across S2-S7. Threshold consts (BARGE_IN_KILL_TIME_P99_MS = 80.0, MOUTH_TO_EAR_P99_MS = 700.0, TICK_LAG_MAX_MS = 10.0, TICK_OVERRUN_PCT_MAX = 1.0, SWEEP_CONCURRENCIES = [1,10,50]) land now per the plan's S1 step 2 note (used by S5/S6/S7 wiring). Adds toml = 0.8 to workspace.dependencies (the first consumer; spec §1.1 claim of pre-existing membership was inaccurate). Task S1 of slice-4½ -- everything else depends on this landing. Signed-off-by: Aaron D. Lee <himself@adlee.work>
This commit is contained in:
421
Cargo.lock
generated
421
Cargo.lock
generated
@@ -34,9 +34,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
@@ -113,9 +113,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"untrusted 0.7.1",
|
||||
@@ -124,14 +124,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.41.0"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -222,6 +223,15 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
@@ -257,9 +267,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.65"
|
||||
version = "1.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -403,7 +413,7 @@ checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
@@ -455,12 +465,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
@@ -474,9 +481,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dimpl"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a11091ebc139b18d6b5878d8769ca43ad5ea8a7e2f5ed1e1f25e172951ef0ce"
|
||||
checksum = "bb2b8390bf0021eae92cf96a0e3a54c531b6315c47edfe9220f5fc61bc9e1f63"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayvec",
|
||||
@@ -514,6 +521,12 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -664,6 +677,12 @@ version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.2"
|
||||
@@ -770,13 +789,116 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -852,6 +974,12 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -947,9 +1075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
@@ -957,9 +1085,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
@@ -1063,6 +1191,15 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -1251,9 +1388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.14.7"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
|
||||
checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"rustls-pki-types",
|
||||
@@ -1342,9 +1479,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
@@ -1384,9 +1521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
@@ -1475,6 +1612,20 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rutster-sim"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"rutster",
|
||||
"rutster-media",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rutster-spend"
|
||||
version = "0.0.0"
|
||||
@@ -1547,9 +1698,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sctp-proto"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5376eaf8a764118abd6bee29673c68eab91f0913b448fe2944e74488b63c37b5"
|
||||
checksum = "199c5c38008c2c151e27afd228230b1b69f849b1f7629f5df86662ee1f456187"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"crc",
|
||||
@@ -1626,6 +1777,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -1712,6 +1872,12 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "str0m"
|
||||
version = "0.21.0"
|
||||
@@ -1860,35 +2026,44 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.11.0"
|
||||
@@ -1954,6 +2129,47 @@ dependencies = [
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -2092,27 +2308,12 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
@@ -2127,13 +2328,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2142,6 +2344,12 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.4"
|
||||
@@ -2361,12 +2569,27 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "x509-cert"
|
||||
version = "0.2.5"
|
||||
@@ -2398,13 +2621,37 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.52"
|
||||
@@ -2425,12 +2672,66 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -6,13 +6,14 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/rutster",
|
||||
"crates/rutster-brain-realtime",
|
||||
"crates/rutster-call-model",
|
||||
"crates/rutster-media",
|
||||
"crates/rutster-trunk",
|
||||
"crates/rutster-sim",
|
||||
"crates/rutster-spend",
|
||||
"crates/rutster-tap",
|
||||
"crates/rutster-tap-echo",
|
||||
"crates/rutster-brain-realtime",
|
||||
"crates/rutster-spend",
|
||||
"crates/rutster-trunk",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
@@ -66,3 +67,7 @@ http = "1"
|
||||
# construction"). `default-features = false` drops the default
|
||||
# native-tls/OpenSSL backend. `json` for the Calls.json response parsing.
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
# toml 0.8: TOML deserialization for the slice-4½ sim-crate's Scenario files
|
||||
# (crates/rutster-sim/scenarios/*.toml). The first consumer of `toml` in
|
||||
# the workspace; declared here so future members share the version pin.
|
||||
toml = "0.8"
|
||||
|
||||
30
crates/rutster-sim/Cargo.toml
Normal file
30
crates/rutster-sim/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
# crates/rutster-sim/Cargo.toml — the self-hostable benchmark + simulation
|
||||
# harness crate (ADR-0010 spearhead step 4½). Default-off `sim-bench`
|
||||
# feature gates the CI-regressed threshold sweep so the routine
|
||||
# `cargo test --all` gate stays fast.
|
||||
[package]
|
||||
name = "rutster-sim"
|
||||
version = "0.0.0"
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Self-hostable benchmark + simulation harness (ADR-0010 spearhead step 4½)."
|
||||
|
||||
[dependencies]
|
||||
rutster-media = { path = "../rutster-media" }
|
||||
rutster = { path = "../rutster" }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
toml = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# The CI-regressed threshold sweep. Default OFF so `cargo test --all` (the
|
||||
# routine gate) stays fast. A separate CI job runs
|
||||
# `cargo test --all --features=sim-bench -- --test-threads=1` per spec §5.4 +
|
||||
# §6.5. A latency regression fails the build the same way a broken test
|
||||
# does (ADR-0010).
|
||||
sim-bench = []
|
||||
11
crates/rutster-sim/src/concurrency.rs
Normal file
11
crates/rutster-sim/src/concurrency.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! # concurrency — `ConcurrencyRunner`: N concurrent `SimCall`s + sweep-report
|
||||
//! aggregation
|
||||
//!
|
||||
//! **Stub — lands in S5.**
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.4 + §4.2 + §2.4 for the design + `docs/superpowers/plans/2026-07-05-slice-4-half-benchmark-sim.md`
|
||||
//! Task S5 for the implementation. Spawns N concurrent `SimCall`s
|
||||
//! (N ∈ `SWEEP_CONCURRENCIES = [1, 10, 50]`) against the same in-process
|
||||
//! `MockRealtimeBrain`, aggregates per-call latencies into the
|
||||
//! `PerConcurrencyReport` rows of the `SweepReport`.
|
||||
8
crates/rutster-sim/src/latency.rs
Normal file
8
crates/rutster-sim/src/latency.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! # latency — post-hoc p50/p99 latency computation from `Capture` events
|
||||
//!
|
||||
//! **Stub — lands in S3.**
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.3 for the design + `docs/superpowers/plans/2026-07-05-slice-4-half-benchmark-sim.md`
|
||||
//! Task S3 for the implementation. Consumes the `Capture` stream from a
|
||||
//! `SimAudioPipe` and computes barge-in kill-time + mouth-to-ear p50/p99.
|
||||
65
crates/rutster-sim/src/lib.rs
Normal file
65
crates/rutster-sim/src/lib.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! # rutster-sim — the self-hostable benchmark + simulation harness
|
||||
//!
|
||||
//! **Status:** spearhead step 4½ (ADR-0010). The wedge's measurement surface.
|
||||
//!
|
||||
//! This crate drives synthetic callers through the SAME media-leg path real
|
||||
//! callers use, measures p50/p99 mouth-to-ear latency + barge-in kill-time
|
||||
//! against slice-4's ≤60 ms kill budget, and runs the same measurements at
|
||||
//! 1 / 10 / 50 concurrent calls. A separate CI job
|
||||
//! (`cargo test --all --features=sim-bench`) asserts thresholds per commit;
|
||||
//! a latency regression fails the build (ADR-0010).
|
||||
//!
|
||||
//! # Why this crate exists (the FOB differentiator)
|
||||
//!
|
||||
//! Slice-4 ships a reflex loop + a synthetic e2e test. SIM-BENCH is the
|
||||
//! artifact that turns arithmetic latency claims into CI-regressed
|
||||
//! measurement. See
|
||||
//! [`docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`]
|
||||
//! for the design.
|
||||
//!
|
||||
//! # Why a separate crate (not in-tree tests)
|
||||
//!
|
||||
//! The harness is hot-path-adjacent + differentiating (ADR-0008 FOB) — it
|
||||
//! earns cratehood the same way `rutster-tap` did. The dep direction is
|
||||
//! clean: `rutster-sim` → `rutster-media` + `rutster`. The harness
|
||||
//! consumes types; it doesn't ride on the binary's internal plumbing.
|
||||
//!
|
||||
//! # Module map (lands across S1-S7)
|
||||
//!
|
||||
//! - [`scenario`] (S1) — `Scenario` + `ScenarioStep` TOML-deserializable
|
||||
//! scripted-caller data types. Determinism is the point.
|
||||
//! - [`sim_audio_pipe`] (S2) — `SimAudioPipe: AudioPipe` test-double that
|
||||
//! IS the caller; captures both clocks (`Instant::now()` at onset +
|
||||
//! receipt). The measurement boundary (spec §2.2).
|
||||
//! - [`latency`] (S3) — `LatencyProbe`: post-hoc p50/p99 kill + mouth-to-ear
|
||||
//! computation from the `Capture` event stream.
|
||||
//! - [`runner`] (S4) — `SimCall` + `ScenarioRunner`: drives one synthetic
|
||||
//! caller end-to-end against the FOB reflex loop standalone in tokio
|
||||
//! (no `MediaThread` extension per S4 standalone-path conclusion).
|
||||
//! - [`concurrency`] (S5) — `ConcurrencyRunner`: N concurrent `SimCall`s
|
||||
//! against the same MockRealtimeBrain; aggregates per-call latencies
|
||||
//! into a `SweepReport`.
|
||||
//! - [`tick_lag`] (S6) — `TickLagGauge`: the ADR-0010 doctrine-drift
|
||||
//! detector. Surfaces `tick_overruns` / `last_tick_micros` in the
|
||||
//! `SweepReport`.
|
||||
//! - [`thresholds`] (S7) — Threshold consts + the `#[cfg(feature =
|
||||
//! "sim-bench")] #[tokio::test]` assertion tests. A latency regression
|
||||
//! fails the build.
|
||||
|
||||
// All modules declared upfront so the lib.rs is stable across task
|
||||
// commits; each module file grows from a `//! stub` header to its full
|
||||
// impl as its task lands. Only the `pub use` re-exports for landed
|
||||
// modules are present — they grow as each task's symbols become available.
|
||||
pub mod concurrency;
|
||||
pub mod latency;
|
||||
pub mod runner;
|
||||
pub mod scenario;
|
||||
pub mod sim_audio_pipe;
|
||||
pub mod thresholds;
|
||||
pub mod tick_lag;
|
||||
|
||||
pub use scenario::{Scenario, ScenarioError, ScenarioStep};
|
||||
pub use thresholds::{
|
||||
BARGE_IN_KILL_TIME_P99_MS, MOUTH_TO_EAR_P99_MS, SWEEP_CONCURRENCIES, TICK_LAG_MAX_MS,
|
||||
TICK_OVERRUN_PCT_MAX,
|
||||
};
|
||||
12
crates/rutster-sim/src/runner.rs
Normal file
12
crates/rutster-sim/src/runner.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! # runner — `SimCall` + `ScenarioRunner`: drive one synthetic caller
|
||||
//! end-to-end
|
||||
//!
|
||||
//! **Stub — lands in S4.**
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.4 + §4.1 for the design + `docs/superpowers/plans/2026-07-05-slice-4-half-benchmark-sim.md`
|
||||
//! Task S4 for the implementation. The SimCall wires itself standalone in
|
||||
//! tokio (no `MediaCmd::RegisterSim` — per the plan's S4 standalone-path
|
||||
//! conclusion, the File Structure table is stale on this point; S4
|
||||
//! supersedes). Composes slice-4's `Reflex<TapAudioPipe>` +
|
||||
//! `LocalVadReflex` stack directly against an in-process `MockRealtimeBrain`.
|
||||
250
crates/rutster-sim/src/scenario.rs
Normal file
250
crates/rutster-sim/src/scenario.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
//! # Scenario — the scripted-caller data type
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.1.
|
||||
//!
|
||||
//! A `Scenario` is a sequence of `ScenarioStep`s read from a TOML file under
|
||||
//! `crates/rutster-sim/scenarios/*.toml`. Deterministic by construction —
|
||||
//! the entire point is reproducible thresholds in CI (LLM-driven callers
|
||||
//! land in a post-spearhead refinement tier; see §1.2).
|
||||
//!
|
||||
//! # Why TOML (not YAML, not RON)
|
||||
//!
|
||||
//! `serde` + `toml` is already a workspace member. TOML keeps the scenario
|
||||
//! file readable as a one-shot script (a sequence of named steps + numbers);
|
||||
//! YAML would invite flow-mapping complexity this format doesn't need.
|
||||
//!
|
||||
//! # Why `#[serde(tag = "kind")]` (internally-tagged enum)
|
||||
//!
|
||||
//! Each step in the scenario TOML is one TOML table:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [[steps]]
|
||||
//! kind = "speak_loud"
|
||||
//! frames = 20
|
||||
//! ```
|
||||
//!
|
||||
//! `serde`'s internally-tagged enum representation (`tag = "kind"`) reads the
|
||||
//! `kind` key to dispatch to the matching enum variant. This is the idiomatic
|
||||
//! shape for "list of named, differently-shaped records" in TOML — the
|
||||
//! alternative (externally-tagged) would require a redundant table layer
|
||||
//! (`[[steps]] variant = { speak_loud = { frames = 20 } }`) that hurts
|
||||
//! readability for no benefit. See <https://serde.rs enum-representations>.
|
||||
//!
|
||||
//! `rename_all = "snake_case"` maps the Rust `SpeakLoud` variant to the
|
||||
//! TOML `speak_loud` tag — matches the convention used in slice-4's
|
||||
//! `AdvisoryEvent` enum (the precedent this file follows).
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// The scripted-caller scenario. Read from a TOML file. Deterministic.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```toml
|
||||
/// name = "loud-barge"
|
||||
///
|
||||
/// [[steps]]
|
||||
/// kind = "speak_loud"
|
||||
/// frames = 20
|
||||
///
|
||||
/// [[steps]]
|
||||
/// kind = "await_reply"
|
||||
/// frames = 0
|
||||
///
|
||||
/// [[steps]]
|
||||
/// kind = "end"
|
||||
/// ```
|
||||
///
|
||||
/// The `SimAudioPipe::new(scenario, ..)` constructor consumes the
|
||||
/// `steps` vector front-to-back during `on_pcm_frame` (the sink path —
|
||||
/// the caller "speaks") and `next_pcm_frame` (the source path — the
|
||||
/// caller "hears" brain replies, advancing `AwaitReply` steps).
|
||||
#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct Scenario {
|
||||
/// Human-readable identifier; surfaces in CI failure messages
|
||||
/// ("scenario loud-barge failed: p99 kill-time 84ms > 80ms").
|
||||
pub name: String,
|
||||
/// Time-ordered sequence of caller actions. The `SimAudioPipe`
|
||||
/// consumes them in order during `on_pcm_frame` (for speak/pause
|
||||
/// steps) and `next_pcm_frame` (for `AwaitReply` barriers).
|
||||
pub steps: Vec<ScenarioStep>,
|
||||
}
|
||||
|
||||
/// One axis of caller behavior. A scenario is a time-ordered sequence
|
||||
/// of these. The `SimAudioPipe` consumes them in order during
|
||||
/// `on_pcm_frame`.
|
||||
///
|
||||
/// # Why an enum (not a struct with a `kind` field)
|
||||
///
|
||||
/// The steps have *different fields* (`SpeakLoud { frames }` vs `End`
|
||||
/// has none). A struct-with-kind-field would require `Option<u32>` for
|
||||
/// every variant-irrelevant field — losing type safety for no ergonomic
|
||||
/// gain. The enum approach makes the variant's payload explicit at the
|
||||
/// type level; `serde`'s internally-tagged representation keeps the TOML
|
||||
/// shape flat + readable.
|
||||
#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ScenarioStep {
|
||||
/// Send N loud PCM frames (sample value 1000, well above
|
||||
/// `VAD_RMS_THRESHOLD = 500.0`). Triggers the local VAD via
|
||||
/// slice-4's `LocalVadReflex::on_pcm_frame` — the primary barge-in
|
||||
/// path (slice-4 §5.1). The `SimAudioPipe`'s sink path emits one
|
||||
/// loud `PcmFrame` per `on_pcm_frame` call while this step is
|
||||
/// active; on step entry it captures `Capture::CallerLoudOnset`.
|
||||
SpeakLoud { frames: u32 },
|
||||
/// Send N zero frames (sample value 0, well below
|
||||
/// `VAD_RMS_THRESHOLD`). Drives the mock-brain advisory path
|
||||
/// (slice-4 §5.2 secondary path): `MockRealtimeBrain` sees
|
||||
/// "no caller audio for M frames" + emits an advisory →
|
||||
/// `Reflex::muted = true`. The wedge cares about LOUD barge
|
||||
/// measurement; quiet onsets are unscored (no `Capture`).
|
||||
SpeakQuiet { frames: u32 },
|
||||
/// Insert N zero frames before the next step (silence pacing).
|
||||
/// Used by the `sustained-call.toml` scenario (5 minutes of talk
|
||||
/// with 3 barges) to space barge cycles apart.
|
||||
Pause { frames: u32 },
|
||||
/// Wait until the harness receives M "ear" frames before advancing.
|
||||
/// Barrier semantics: brain's reply must arrive before the next
|
||||
/// caller action. The `SimAudioPipe`'s source path
|
||||
/// (`next_pcm_frame`) decrements this counter for each `Some(frame)`
|
||||
/// returned; on reaching zero, advances.
|
||||
AwaitReply { frames: u32 },
|
||||
/// End the scenario. The `SimAudioPipe`'s `next_pcm_frame` returns
|
||||
/// `None` thereafter; the `SimCall` (S4) detects end-of-scenario +
|
||||
/// terminates its tick loop.
|
||||
End,
|
||||
}
|
||||
|
||||
/// Errors surfaced during scenario loading. Cold-path; OK to be
|
||||
/// `thiserror`-derived (the hot path goes through
|
||||
/// `SimAudioPipe::on_pcm_frame` which never reads files).
|
||||
///
|
||||
/// `#[from]` on the variants auto-implements `From<io::Error>` and
|
||||
/// `From<toml::de::Error>` so `?`-propagation Just Works in
|
||||
/// `Scenario::load`.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ScenarioError {
|
||||
#[error("scenario file read failed: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("scenario TOML parse failed: {0}")]
|
||||
Parse(#[from] toml::de::Error),
|
||||
}
|
||||
|
||||
impl Scenario {
|
||||
/// Load a scenario from a TOML file. Cold-path.
|
||||
///
|
||||
/// Wraps `std::fs::read_to_string` + `toml::from_str` behind the
|
||||
/// `ScenarioError` enum so callers can `?`-propagate both failure
|
||||
/// modes uniformly. The `path: impl AsRef<Path>` bound follows
|
||||
/// the std-library convention: it accepts `&str`, `String`,
|
||||
/// `PathBuf`, `&Path` — matching how scenarios are loaded from
|
||||
/// CLI args or test fixtures.
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<Self, ScenarioError> {
|
||||
let raw = std::fs::read_to_string(path)?;
|
||||
Self::from_toml(&raw)
|
||||
}
|
||||
|
||||
/// Parse a scenario from an in-memory TOML string.
|
||||
///
|
||||
/// Split out from `load` so unit tests can construct scenarios
|
||||
/// without touching the filesystem (filesystem-isolated unit
|
||||
/// tests are the std pattern in this codebase — see slice-4's
|
||||
/// `reflex.rs` tests).
|
||||
pub fn from_toml(s: &str) -> Result<Self, ScenarioError> {
|
||||
Ok(toml::from_str(s)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn scenario_parses_minimal_end_only() {
|
||||
// The trivial scenario: just one `End` step. Exercises the
|
||||
// internally-tagged enum's bare-variant shape (`kind = "end"`
|
||||
// with no payload fields).
|
||||
let toml = r#"
|
||||
name = "trivial"
|
||||
[[steps]]
|
||||
kind = "end"
|
||||
"#;
|
||||
let s = Scenario::from_toml(toml).expect("parse");
|
||||
assert_eq!(s.name, "trivial");
|
||||
assert_eq!(s.steps, vec![ScenarioStep::End]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scenario_parses_loud_barge_shape() {
|
||||
// The canonical loud-barge scenario from spec §5.3. Verifies
|
||||
// the three-step shape (speak_loud → await_reply → end) parses
|
||||
// to the expected variant sequence with payload fields intact.
|
||||
let toml = r#"
|
||||
name = "loud-barge"
|
||||
[[steps]]
|
||||
kind = "speak_loud"
|
||||
frames = 20
|
||||
[[steps]]
|
||||
kind = "await_reply"
|
||||
frames = 0
|
||||
[[steps]]
|
||||
kind = "end"
|
||||
"#;
|
||||
let s = Scenario::from_toml(toml).expect("parse");
|
||||
assert_eq!(s.name, "loud-barge");
|
||||
assert_eq!(
|
||||
s.steps,
|
||||
vec![
|
||||
ScenarioStep::SpeakLoud { frames: 20 },
|
||||
ScenarioStep::AwaitReply { frames: 0 },
|
||||
ScenarioStep::End,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scenario_parses_sustained_call_shape() {
|
||||
// The sustained-call scenario (spec §5.3 entry #3) alternates
|
||||
// speak_loud + speak_quiet. Verifies both payload-bearing
|
||||
// variants parse correctly in sequence.
|
||||
let toml = r#"
|
||||
name = "sustained"
|
||||
[[steps]]
|
||||
kind = "speak_loud"
|
||||
frames = 10
|
||||
[[steps]]
|
||||
kind = "speak_quiet"
|
||||
frames = 10
|
||||
[[steps]]
|
||||
kind = "speak_loud"
|
||||
frames = 10
|
||||
[[steps]]
|
||||
kind = "end"
|
||||
"#;
|
||||
let s = Scenario::from_toml(toml).expect("parse");
|
||||
assert_eq!(s.steps.len(), 4);
|
||||
assert!(matches!(s.steps[0], ScenarioStep::SpeakLoud { frames: 10 }));
|
||||
assert!(matches!(
|
||||
s.steps[1],
|
||||
ScenarioStep::SpeakQuiet { frames: 10 }
|
||||
));
|
||||
assert!(matches!(s.steps[2], ScenarioStep::SpeakLoud { frames: 10 }));
|
||||
assert!(matches!(s.steps[3], ScenarioStep::End));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scenario_unknown_kind_errors() {
|
||||
// An unknown `kind` tag (typo, future-extension tag, etc.)
|
||||
// must surface as a `Parse` error rather than silently
|
||||
// defaulting. This is the contract `serde`'s internally-tagged
|
||||
// enum provides: unknown tags fail the deserialize rather
|
||||
// than producing a `None`-ish default.
|
||||
let toml = r#"
|
||||
name = "bad"
|
||||
[[steps]]
|
||||
kind = "ship_a_real_caller"
|
||||
"#;
|
||||
assert!(Scenario::from_toml(toml).is_err());
|
||||
}
|
||||
}
|
||||
9
crates/rutster-sim/src/sim_audio_pipe.rs
Normal file
9
crates/rutster-sim/src/sim_audio_pipe.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! # sim_audio_pipe — the test-double `AudioPipe` that simulates a caller
|
||||
//!
|
||||
//! **Stub — lands in S2.**
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.2 for the design + `docs/superpowers/plans/2026-07-05-slice-4-half-benchmark-sim.md`
|
||||
//! Task S2 for the implementation. Drives a `Scenario` on `on_pcm_frame`
|
||||
//! (sink: caller speaks); captures `Instant::now()` at every meaningful
|
||||
//! event for the `LatencyProbe`.
|
||||
58
crates/rutster-sim/src/thresholds.rs
Normal file
58
crates/rutster-sim/src/thresholds.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! # thresholds — CI-regressed latency thresholds + sim-bench assertion tests
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §5.1 + §5.5.
|
||||
//!
|
||||
//! The threshold consts land here at S1 (per the plan's S1 step 2 note:
|
||||
//! "the consts as immediate module-level `pub const` items per spec §5.1 —
|
||||
//! they're used by S5/S6/S7 wiring"). The `#[cfg(feature = "sim-bench")]
|
||||
//! #[tokio::test]` assertion tests land at S7.
|
||||
//!
|
||||
//! # Why these numbers
|
||||
//!
|
||||
//! See spec §5.1 for the budget-vs-assertion-slack reasoning. Each const
|
||||
//! is paired with a doc-comment explaining the budget it enforces + the
|
||||
//! slack rationale (so a future maintainer who needs to bump one knows
|
||||
//! *why* the current value is what it is, not just *what* it is).
|
||||
|
||||
/// Slice-4 spec §5.1 + §7 done-criteria #8: kill-time budget is
|
||||
/// ≤60 ms (3 debounce frames × 20 ms tick + 1 tick to drain + apply).
|
||||
/// Observer slack to make CI deterministic-but-not-flaky on a slow
|
||||
/// runner: effective CI assertion ≤80 ms (60 ms budget + 20 ms slack).
|
||||
///
|
||||
/// A regression here is the red X ADR-0010 demands — the wedge's
|
||||
/// "local real-time reflexes that don't need the brain" claim is
|
||||
/// arithmetic until this assertion fires on every PR.
|
||||
pub const BARGE_IN_KILL_TIME_P99_MS: f64 = 80.0;
|
||||
|
||||
/// Slice-1 + slice-3 mouth-to-ear budget: 200 ms (slice-1 notification
|
||||
/// budget) + 250 ms mock brain round-trip + 100 ms playout buffer.
|
||||
/// CI assertion ceiling: 700 ms (allowance for CI runner variance
|
||||
/// against the dev machine — the mock brain is deterministic but the
|
||||
/// harness adds observer cost; the dev machine usually lands ~600 ms).
|
||||
pub const MOUTH_TO_EAR_P99_MS: f64 = 700.0;
|
||||
|
||||
/// Slice-5/seams tick-lag gauge: the meta-tick must stay under 10 ms
|
||||
/// (the loop's nominal period). At 1 call: ≤2 ms expected. At 50
|
||||
/// calls: ≤10 ms expected. Tick overruns (count of ticks exceeding
|
||||
/// 10 ms) at p50 across the sweep: ≤1% of total ticks per
|
||||
/// `TICK_OVERRUN_PCT_MAX`.
|
||||
///
|
||||
/// If a concurrency sweep shows `tick_overrun_pct > 1.0` at 50 calls,
|
||||
/// **the FOB reflex loop's single-thread debt is real and the
|
||||
/// dedicated-threadpool-shard graduation (slice-4 §1.2 deferral #2)
|
||||
/// gets its data-driven case.** That finding is the slice's
|
||||
/// load-bearing output regardless of whether the latency thresholds
|
||||
/// pass — the doctrine-drift detector worked.
|
||||
pub const TICK_LAG_MAX_MS: f64 = 10.0;
|
||||
pub const TICK_OVERRUN_PCT_MAX: f64 = 1.0;
|
||||
|
||||
/// Concurrency-sweep sample sizes per spec §2.4: 1 isolates the
|
||||
/// baseline (cold-path latency with zero concurrency pressure —
|
||||
/// slice-4's §5.1 ≤60 ms kill budget asserted here); 10 is the
|
||||
/// warm working set (~peak spearhead-scale); 50 is the saturation
|
||||
/// point (ADR-0010's "single-poll-task head-of-line-blocking debt"
|
||||
/// lives here). We do NOT test 100/500/5000 — that's fleet-scale
|
||||
/// (rung 3). 50 is the upper edge of the spearhead's "one binary,
|
||||
/// one city" claim.
|
||||
pub const SWEEP_CONCURRENCIES: &[usize] = &[1, 10, 50];
|
||||
13
crates/rutster-sim/src/tick_lag.rs
Normal file
13
crates/rutster-sim/src/tick_lag.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! # tick_lag — `TickLagGauge`: the ADR-0010 doctrine-drift detector
|
||||
//!
|
||||
//! **Stub — lands in S6.**
|
||||
//!
|
||||
//! See `docs/superpowers/specs/2026-07-05-slice-4-half-benchmark-sim-design.md`
|
||||
//! §3.6 + §6.4 for the design + `docs/superpowers/plans/2026-07-05-slice-4-half-benchmark-sim.md`
|
||||
//! Task S6 for the implementation. Surfaces `MediaStats.{tick_overruns,
|
||||
//! last_tick_micros}` as primary readouts in the `SweepReport`. The
|
||||
//! concurrency sweep turns the gauge from a counter into a decision
|
||||
//! artifact: "does single-thread poll loop breach budget at realistic
|
||||
//! concurrency?" gets answered with data, not vibes. If yes, the
|
||||
//! dedicated threadpool-shard graduation (slice-4 deferral #2) gets
|
||||
//! scheduled on evidence.
|
||||
Reference in New Issue
Block a user