#[path = "../support/workspace_root.rs"]
#[allow(dead_code)]
mod workspace_root;
use std::{
collections::{BTreeMap, BTreeSet},
fs,
path::{Path, PathBuf},
};
struct LockTable {
name: String,
fields: Vec<(String, String)>,
}
const UNSTAGED_DEFECT_VENDORS: &[(&str, &str)] = &[
(
"alignkit",
"the chordai wav2vec2 aligner is not staged by any shard: `audio::align`'s model gates \
(ALIGNKIT_TEST_MODELS) are local/dev gates and MODELS_LOCK has no table for it",
),
(
"argmax-speakerkit",
"argmaxinc/speakerkit-coreml declares NO license on Hugging Face, so \
undeclared-means-all-rights-reserved applies to those converted graphs and this repository \
deliberately does not fetch them in CI at all (NOTICE section 4). Adding a shard for them \
would reverse a licensing decision, not just widen coverage.",
),
];
const GRAPHLESS_VENDORS: &[&str] = &["tokenizers"];
const CHECKSUMLESS_KITS: &[(&str, &str)] = &[
(
"whisper",
"neither argmaxinc/whisperkit-coreml nor openai/whisper-tiny publishes digests in any form, \
and both tables are still on `revision = \"main\"` (MODELS_LOCK's LOUD FOLLOW-UP), so there \
is nothing to verify against that would mean anything. Their bytes are covered by the \
whisper gates' own model_io pins and by the fp16_guards graph sweep.",
),
(
"lid",
"aufklarer/SpeechBrain-ECAPA-VoxLingua107-21M-CoreML ships NO CHECKSUMS.sha256: its per-file \
digests live in `artifact_manifest.json`, which `shasum -c` cannot read. Pointing \
`checksum-file` at it would fail on a correct tree. The bytes are covered instead by \
the committed manifest `MODELS_LOCK.d/lid@<revision>.sha256`, which tests/lid/common/mod.rs \
reads through `artifact_sha256()` — an EXACT file set, so a missing or an added \
file reds too — verified by `artifact_matches_the_pinned_sha_manifest`, which this shard \
runs, and by the fp16_guards graph sweep.",
),
];
fn repo_files() -> Option<(PathBuf, PathBuf)> {
let root = workspace_root::try_workspace_root()?;
let lock = root.join("MODELS_LOCK");
let workflow = root.join(".github/workflows/ci.yml");
if lock.is_file() && workflow.is_file() {
Some((lock, workflow))
} else {
eprintln!("models_lock checks skipped: not in the repository workspace");
None
}
}
fn stage_script() -> PathBuf {
let root =
workspace_root::try_workspace_root().expect("repo_files() already found the workspace");
let script = root.join(".github/actions/stage-models/stage.sh");
assert!(
script.is_file(),
"{} does not exist — both workflows stage their models through it, so its absence means CI \
downloads nothing (or parses the lock somewhere new)",
script.display()
);
script
}
const GATE_GUARD: &str = "if: ${{ !cancelled() && steps.download.outcome != 'failure' }}";
fn model_tests_steps(ci: &str) -> Vec<String> {
let job = model_tests_job(ci);
let mut steps: Vec<String> = Vec::new();
for line in job.lines() {
let body = line.trim_start();
if !body.is_empty() && line.len() - body.len() == 2 {
break;
}
if ["- name:", "- uses:", "- run:"].iter().any(|start| {
line
.strip_prefix(" ")
.is_some_and(|l| l.starts_with(start))
}) {
steps.push(String::new());
}
if let Some(step) = steps.last_mut() {
step.push_str(line);
step.push('\n');
}
}
steps
}
fn model_tests_job(ci: &str) -> &str {
ci.split_once("\n model-tests:\n")
.expect("ci.yml has no `model-tests` job")
.1
}
fn matrix_rows(ci: &str) -> Vec<BTreeMap<String, String>> {
let matrix = model_tests_job(ci)
.split_once("\n include:\n")
.expect("ci.yml's model-tests job has no `strategy.matrix.include`")
.1;
let mut rows: Vec<BTreeMap<String, String>> = Vec::new();
let mut block: Option<(String, Vec<String>)> = None;
for line in matrix.lines() {
let body = line.trim_start();
let indent = line.len() - body.len();
if block.is_some() {
if body.is_empty() || indent > 12 {
let (_, lines) = block.as_mut().expect("block is Some");
lines.push(body.to_string());
continue;
}
let (key, lines) = block.take().expect("block is Some");
rows
.last_mut()
.expect("a block scalar belongs to a row")
.insert(key, lines.join("\n"));
}
if !body.is_empty() && indent < 10 {
break;
}
if body.is_empty() || body.starts_with('#') {
continue;
}
if indent == 10 {
let kit = body
.strip_prefix("- kit: ")
.unwrap_or_else(|| panic!("ci.yml matrix row does not begin with `- kit: `: {line:?}"));
let mut row = BTreeMap::new();
row.insert("kit".to_string(), kit.trim().to_string());
rows.push(row);
continue;
}
assert_eq!(
indent, 12,
"ci.yml matrix line has an unexpected indent; this reader needs 10 for a row and 12 for its \
keys: {line:?}"
);
let (key, value) = body
.split_once(": ")
.or_else(|| body.strip_suffix(':').map(|k| (k, "")))
.unwrap_or_else(|| panic!("ci.yml matrix line is not a `key: value`: {line:?}"));
let row = rows
.last_mut()
.unwrap_or_else(|| panic!("ci.yml matrix key outside any row: {line:?}"));
if value.trim() == "|" {
block = Some((key.to_string(), Vec::new()));
continue;
}
row.insert(key.to_string(), unquote(value.trim()).to_string());
}
if let Some((key, lines)) = block.take() {
rows
.last_mut()
.expect("a block scalar belongs to a row")
.insert(key, lines.join("\n"));
}
assert!(
!rows.is_empty(),
"ci.yml's model-tests matrix parsed to no rows — either the job lost its matrix, or this \
reader stopped matching its layout"
);
rows
}
fn unquote(value: &str) -> &str {
value
.strip_prefix('\'')
.and_then(|v| v.strip_suffix('\''))
.unwrap_or(value)
}
fn row_field<'a>(row: &'a BTreeMap<String, String>, key: &str) -> &'a str {
row.get(key).map_or("", String::as_str)
}
fn require_field<'a>(row: &'a BTreeMap<String, String>, key: &str) -> &'a str {
let kit = row_field(row, "kit");
let value = row_field(row, key);
assert!(
!value.is_empty(),
"ci.yml's {kit:?} matrix row has no {key:?}; every shard must declare one"
);
value
}
fn field<'a>(table: &'a LockTable, key: &str) -> Option<&'a str> {
table
.fields
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
fn parse_lock(contents: &str) -> Vec<LockTable> {
let mut tables: Vec<LockTable> = Vec::new();
for line in contents.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(name) = line.strip_prefix("[\"").and_then(|s| s.strip_suffix("\"]")) {
tables.push(LockTable {
name: name.to_string(),
fields: Vec::new(),
});
continue;
}
let Some(table) = tables.last_mut() else {
continue; };
let (key, value) = line
.split_once('=')
.unwrap_or_else(|| panic!("MODELS_LOCK: not a table header or `key = value`: {line:?}"));
let key = key.trim().to_string();
let value = value.trim();
let value = value
.strip_prefix('"')
.and_then(|v| v.strip_suffix('"'))
.unwrap_or_else(|| {
panic!("MODELS_LOCK: value for {key:?} is not a quoted string: {value:?}")
});
table.fields.push((key, value.to_string()));
}
tables
}
fn vendor_of(local_dir: &str) -> &str {
local_dir
.strip_prefix("Models/")
.unwrap_or_else(|| {
panic!("MODELS_LOCK `local-dir` {local_dir:?} does not start with `Models/`")
})
.split('/')
.next()
.expect("split always yields one element")
}
fn fp16_pinned_vendors() -> BTreeSet<String> {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fp16_guards.rs");
let source = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let vendors: BTreeSet<String> = [
"const KNOWN_DEFECTS: &[KnownDefect] = &[",
"const LOAD_BEARING_NORMS: &[LoadBearingNorm] = &[",
]
.into_iter()
.flat_map(|decl| pin_vendors_in(&source, decl))
.collect();
assert!(
!vendors.is_empty(),
"no `path:` entries parsed out of tests/fp16_guards.rs's pin registers — the sweep's roster \
moved or this reader stopped matching it, and every coverage claim below would be vacuous"
);
vendors
}
fn pin_vendors_in(source: &str, decl: &str) -> BTreeSet<String> {
let block = source
.split_once(decl)
.unwrap_or_else(|| panic!("tests/fp16_guards.rs no longer declares `{decl}`"))
.1
.split_once("\n];")
.unwrap_or_else(|| panic!("tests/fp16_guards.rs's `{decl}` list is unterminated"))
.0;
let vendors: BTreeSet<String> = block
.lines()
.filter_map(|line| line.trim().strip_prefix("path: \""))
.map(|rest| {
let path = rest
.strip_suffix("\",")
.unwrap_or_else(|| panic!("`{decl}` path entry is not `path: \"...\",`: {rest:?}"));
path
.split('/')
.next()
.expect("split always yields one element")
.to_string()
})
.collect();
assert!(
!vendors.is_empty(),
"no `path:` entries parsed out of `{decl}` in tests/fp16_guards.rs"
);
vendors
}
#[test]
fn lock_parses_and_every_table_is_complete() {
let Some((lock_path, _)) = repo_files() else {
return;
};
let contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&contents);
assert!(
!tables.is_empty(),
"MODELS_LOCK parsed to no tables — the lock lost its `[\"repo/name\"]` shape, or this reader \
stopped matching it"
);
for table in &tables {
let has_selector = field(table, "include").is_some() || field(table, "files").is_some();
assert!(
has_selector,
"MODELS_LOCK: table {:?} has neither `include` nor `files`",
table.name
);
for key in ["kit", "revision", "local-dir"] {
assert!(
field(table, key).is_some_and(|v| !v.is_empty()),
"MODELS_LOCK: table {:?} has no {key:?}. Since ci.yml selects tables by `kit` rather than \
by index, a table missing one is unreachable: no shard would ever download it, and the \
lock would document a download CI never performs.",
table.name
);
}
}
}
#[test]
fn ci_workflow_derives_downloads_from_the_lock_instead_of_hardcoding_them() {
let Some((lock_path, workflow_path)) = repo_files() else {
return;
};
let lock_contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&lock_contents);
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let stage_path = stage_script();
let stage = fs::read_to_string(&stage_path)
.unwrap_or_else(|e| panic!("read {}: {e}", stage_path.display()));
for table in &tables {
for (what, text) in [("ci.yml", &ci_contents), ("stage.sh", &stage)] {
assert!(
!text.contains(&table.name),
"{what} hardcodes locked repo {:?}; it must be derived from parsing MODELS_LOCK at \
runtime instead",
table.name
);
}
}
assert!(
ci_contents.contains("MODELS_LOCK"),
"ci.yml's model-tests job never references MODELS_LOCK"
);
assert!(
ci_contents.contains("uses: ./.github/actions/stage-models"),
"ci.yml's model-tests job no longer calls .github/actions/stage-models, the one MODELS_LOCK \
parser both workflows share"
);
assert!(
!ci_contents.contains("hf download"),
"ci.yml is staging models inline again. That is a second parser over one lock file, free to \
drift with nothing failing when it does — and the tree it caches is verified table by table \
by nothing, because stage.sh's per-table manifest checks exist on ITS download path alone. \
Stage through .github/actions/stage-models instead."
);
for needle in [
"hf download \"${download_args[@]}\"",
"argv=(\"$repo\" \"${args[@]}\" --revision \"$revision\" --local-dir \"$localdir\")",
"-v want=\"$kit\"",
] {
assert!(
stage.contains(needle),
"{} no longer builds its `hf download` from the lock ({needle:?} is absent), so editing \
MODELS_LOCK would stop changing what CI fetches",
stage_path.display()
);
}
assert!(
stage.contains("grep -cE '^kit[[:space:]]*=' \"$lock\""),
"stage.sh no longer counts MODELS_LOCK's `kit` fields against its table count, so a table \
added (or a tag deleted) would be silently unreachable — the failure the old `table_count` \
pin existed to prevent"
);
assert!(
stage.contains("[ \"$table_count\" -ne \"$kit_count\" ]"),
"stage.sh counts kits but no longer compares them to the table count"
);
assert!(
stage.contains("defines no table with kit"),
"stage.sh no longer refuses a kit that matches no MODELS_LOCK table, so a matrix row with no \
table would stage nothing and then gate a bare checkout"
);
assert!(
ci_contents.contains("MODELS_LOCK defines no table with kit"),
"ci.yml must refuse a shard whose kit matches no MODELS_LOCK table in a step that runs on \
cache hits too — otherwise a matrix row with no table would silently gate an empty tree \
whenever the cache was warm"
);
}
#[test]
fn ci_shards_every_kit_in_the_lock() {
let Some((lock_path, workflow_path)) = repo_files() else {
return;
};
let lock_contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&lock_contents);
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let rows = matrix_rows(&ci_contents);
let lock_kits: BTreeSet<&str> = tables
.iter()
.map(|t| field(t, "kit").expect("checked by lock_parses_and_every_table_is_complete"))
.collect();
let shard_kits: BTreeSet<&str> = rows.iter().map(|r| row_field(r, "kit")).collect();
assert_eq!(
shard_kits.len(),
rows.len(),
"ci.yml's model-tests matrix has two shards for the same kit; they would race for one \
Models/ tree and one cache key"
);
assert_eq!(
lock_kits, shard_kits,
"MODELS_LOCK's kits and ci.yml's model-tests shards have drifted apart. A kit in the lock \
with no shard is a download nothing performs; a shard with no table downloads nothing and \
then gates a bare checkout. Add the missing table or the missing matrix row."
);
let checksumless: BTreeMap<&str, &str> = CHECKSUMLESS_KITS.iter().copied().collect();
assert_eq!(
checksumless.len(),
CHECKSUMLESS_KITS.len(),
"CHECKSUMLESS_KITS lists a kit twice; the second reason would be unreachable"
);
let mut declared_checksumless: BTreeSet<&str> = BTreeSet::new();
for row in &rows {
let kit = row_field(row, "kit");
let local_dirs: Vec<&str> = tables
.iter()
.filter(|t| field(t, "kit") == Some(kit))
.map(|t| field(t, "local-dir").expect("checked above"))
.collect();
let mut paths: Vec<&str> = require_field(row, "probe").split_whitespace().collect();
let checksum_dir = require_field(row, "checksum-dir");
if checksum_dir == "none" {
let reason = checksumless.get(kit).unwrap_or_else(|| {
panic!(
"ci.yml's {kit:?} shard declares `checksum-dir: none`, but CHECKSUMLESS_KITS does not \
record that kit's repos as shipping no shasum-readable CHECKSUMS.sha256 (it records \
{:?}). Either a verification was dropped, or the absence is real — in which case add \
{kit:?} here WITH the reason and the coverage that stands in for it.",
checksumless.keys().collect::<Vec<_>>()
)
});
assert!(
!reason.trim().is_empty(),
"CHECKSUMLESS_KITS records {kit:?} with an empty reason; the reason is the declaration"
);
declared_checksumless.insert(kit);
} else {
paths.push(checksum_dir);
assert!(
!require_field(row, "checksum-file").is_empty(),
"ci.yml's {kit:?} shard names a checksum directory but no checksum file"
);
}
for path in paths {
assert!(
local_dirs
.iter()
.any(|dir| path == *dir || path.starts_with(&format!("{dir}/"))),
"ci.yml's {kit:?} shard names {path:?}, which is not under any of that kit's MODELS_LOCK \
`local-dir` destinations ({local_dirs:?}) — the shard would probe or verify a tree its \
own download never writes"
);
}
let filter = row_field(row, "checksum-filter");
let lines = row_field(row, "checksum-lines");
assert_eq!(
filter.is_empty(),
lines.is_empty(),
"ci.yml's {kit:?} shard has a checksum-filter without a checksum-lines pin (or the \
reverse); the count is what makes the filter non-vacuous"
);
if !lines.is_empty() {
assert!(
lines.parse::<u32>().is_ok_and(|n| n > 0),
"ci.yml's {kit:?} shard pins checksum-lines at {lines:?}, which is not a positive count"
);
}
let gates = require_field(row, "gates");
let groups: Vec<&str> = gates.lines().filter(|l| !l.trim().is_empty()).collect();
assert!(
!groups.is_empty(),
"ci.yml's {kit:?} shard has an empty gate plan"
);
for group in groups {
let mut parts = group.splitn(3, '|');
let features = parts.next().unwrap_or_default();
let selectors = parts.next().unwrap_or_default();
assert!(
selectors.split_whitespace().next().is_some(),
"ci.yml's {kit:?} shard has a gate group with no test selector: {group:?} (features \
{features:?})"
);
}
}
for (kit, _) in CHECKSUMLESS_KITS {
assert!(
shard_kits.contains(kit),
"CHECKSUMLESS_KITS names {kit:?}, which has no model-tests shard in ci.yml. The exemption \
describes a shard that no longer exists — drop the entry."
);
assert!(
declared_checksumless.contains(kit),
"CHECKSUMLESS_KITS records {kit:?} as staging no repo with a shasum-readable \
CHECKSUMS.sha256, but its shard now names a checksum-dir. If the repo grew one, that is \
good news — delete this entry so the exemption cannot outlive its reason and shelter the \
next verification somebody drops."
);
}
}
#[test]
fn ci_runs_the_ced_gates_for_exactly_the_size_the_lock_stages() {
let Some((lock_path, workflow_path)) = repo_files() else {
return;
};
let lock_contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&lock_contents);
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let ced = tables
.iter()
.find(|t| field(t, "kit") == Some("ced"))
.expect("MODELS_LOCK has no `ced` kit table");
let include = field(ced, "include").expect("the CED table has an `include` selector");
let size = include
.strip_prefix("ced-")
.and_then(|rest| rest.strip_suffix("/*"))
.unwrap_or_else(|| {
panic!(
"MODELS_LOCK's CED `include` is {include:?}; this pin needs the single-size \
`ced-<size>/*` shape so the staged size can be matched against the shard's gate filter"
)
});
assert!(
!size.is_empty() && size.chars().all(|c| c.is_ascii_lowercase()),
"MODELS_LOCK's CED `include` names {size:?}, which is not a CedModel size name"
);
assert_eq!(
field(ced, "local-dir"),
Some("Models/ced"),
"MODELS_LOCK doesn't stage CED into Models/ced, the family root tests/ced/common/mod.rs \
resolves without CED_TEST_MODELS"
);
let row = matrix_rows(&ci_contents)
.into_iter()
.find(|r| row_field(r, "kit") == "ced")
.expect("ci.yml has no `ced` shard");
let bundle = format!("Models/ced/ced-{size}/ced_{size}.mlmodelc");
for key in ["probe", "checksum-dir"] {
assert!(
row_field(&row, key).split_whitespace().any(|p| p == bundle),
"MODELS_LOCK stages CED size {size:?}, but the ced shard's {key:?} does not name its bundle \
{bundle:?} — the absent-artifact guard and the checksum verification would be pointed at a \
different size than the one downloaded"
);
}
let filters: Vec<&str> = row_field(&row, "gates")
.lines()
.filter(|l| !l.trim().is_empty())
.map(|group| group.splitn(3, '|').nth(2).unwrap_or_default())
.collect();
assert_eq!(
filters,
vec![format!("{size}::")],
"the ced shard's gate plan must filter every group on {size:?}, the one size MODELS_LOCK \
stages"
);
assert!(
ci_contents.contains("-- --list --ignored ${filter:+\"$filter\"}")
&& ci_contents.contains("-- --ignored ${filter:+\"$filter\"}"),
"ci.yml's gate runner no longer applies the plan's filter to BOTH the anti-vacuum `--list` \
count and the run, so a deleted or renamed `{size}` module could still count the other \
sizes' gates and then run none"
);
}
fn ignored_gates(src_root: &Path, dir: &Path) -> Vec<(String, String)> {
let mut gates: Vec<(String, String)> = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(current) = stack.pop() {
let entries = fs::read_dir(¤t)
.unwrap_or_else(|e| panic!("{} reads: {e}", current.display()))
.map(|e| e.expect("a directory entry reads").path());
for path in entries {
if path.is_dir() {
stack.push(path);
continue;
}
if path.extension().is_none_or(|ext| ext != "rs") {
continue;
}
let module = path
.strip_prefix(src_root)
.expect("scanned under src/")
.with_extension("")
.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.filter(|c| c != "mod")
.collect::<Vec<_>>()
.join("::");
let contents = fs::read_to_string(&path).expect("a source file reads");
let lines: Vec<&str> = contents.lines().collect();
for (i, line) in lines.iter().enumerate() {
let Some(rest) = line.trim_start().strip_prefix("#[ignore") else {
continue;
};
assert!(
rest.trim_end().ends_with(']'),
"{}:{}: this reader needs the whole `#[ignore ...]` attribute on one line",
path.display(),
i + 1
);
let reason = match rest.split_once('"').and_then(|(_, r)| r.rsplit_once('"')) {
Some((reason, _)) => reason.to_string(),
None => String::new(),
};
let name = lines[i + 1..]
.iter()
.find_map(|l| l.trim_start().strip_prefix("fn "))
.and_then(|l| l.split(['(', '<']).next())
.unwrap_or_else(|| {
panic!(
"{}:{}: an `#[ignore]` attribute with no `fn` after it",
path.display(),
i + 1
)
});
gates.push((format!("{module}::{name}"), reason));
}
}
}
gates.sort();
gates
}
#[test]
fn ci_speaker_lib_gates_skip_exactly_the_unstaged_argmax_tree() {
let Some((_, workflow_path)) = repo_files() else {
return;
};
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let row = matrix_rows(&ci_contents)
.into_iter()
.find(|r| row_field(r, "kit") == "speaker")
.expect("ci.yml has no `speaker` shard");
let lib_group = require_field(&row, "gates")
.lines()
.find(|group| {
group
.split('|')
.nth(1)
.is_some_and(|selectors| selectors.split_whitespace().any(|s| s == "@lib"))
})
.map(str::to_string)
.expect(
"ci.yml's speaker shard has no `@lib` gate group. The library's own speaker model gates \
read exactly the two graphs this shard already stages, so dropping the group means the \
shard downloads for them and then runs none of them.",
);
let filter = lib_group.splitn(3, '|').nth(2).unwrap_or_default();
let skips: Vec<&str> = filter
.split_whitespace()
.map(|token| {
token.strip_prefix("--skip=").unwrap_or_else(|| {
panic!(
"ci.yml's speaker `@lib` group has the filter {filter:?}; this pin models libtest's \
`--skip=<substring>` exclusions only, and a token of another shape would change which \
gates run without changing what is checked here"
)
})
})
.collect();
assert!(
!skips.is_empty(),
"ci.yml's speaker `@lib` group has no filter, so it would run the eleven gates that load \
Models/argmax-speakerkit — a tree no runner fetches (see UNSTAGED_DEFECT_VENDORS)"
);
let src_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
let gates = ignored_gates(&src_root, &src_root.join("audio/speaker"));
assert!(
gates.len() > 30,
"only {} `#[ignore]`d gates found under src/audio/speaker; this reader has stopped matching \
the source layout, and every assertion below would pass vacuously",
gates.len()
);
let mut needs_argmax = 0usize;
for (name, reason) in &gates {
let skipped = skips.iter().any(|s| name.contains(s));
if reason.contains("argmax") {
needs_argmax += 1;
assert!(
skipped,
"the in-lib gate {name:?} says it needs the argmax tree ({reason:?}), but the speaker \
shard's `@lib` filter {filter:?} does not skip it. CI deliberately does not fetch that \
tree (UNSTAGED_DEFECT_VENDORS), so the shard would fail on a missing bundle. Name the \
gate so an existing `--skip=` substring matches it, or add one."
);
} else {
assert!(
!skipped,
"the speaker shard's `@lib` filter {filter:?} skips {name:?}, which needs only the \
speakerkit tree this shard stages ({reason:?}). A skip that widens onto a runnable gate \
drops it from CI silently: the anti-vacuum `--list` count is taken through this same \
filter, so it falls with the run instead of reaching zero."
);
}
}
assert!(
needs_argmax > 0,
"no in-lib speaker gate names argmax in its `#[ignore]` reason, so the filter this shard \
carries is excluding gates for a dependency nothing declares any more"
);
for skip in &skips {
assert!(
gates
.iter()
.any(|(name, reason)| name.contains(skip) && reason.contains("argmax")),
"the speaker shard's `@lib` filter skips {skip:?}, which matches no in-lib gate that needs \
the argmax tree — a stale exclusion can only be hiding a gate CI could run"
);
}
}
#[test]
fn ci_stages_the_speakerkit_overlay_last_and_proves_it_won() {
let Some((lock_path, workflow_path)) = repo_files() else {
return;
};
let lock_contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&lock_contents);
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let index_of = |name: &str| {
tables
.iter()
.position(|t| t.name == name)
.unwrap_or_else(|| panic!("MODELS_LOCK has no {name:?} table"))
};
let base = index_of("FluidInference/speaker-diarization-coreml");
let overlay = index_of("FinDIT-Studio/speakerkit-coreml");
for (index, name) in [(base, "base"), (overlay, "overlay")] {
assert_eq!(
field(&tables[index], "kit"),
Some("speaker"),
"MODELS_LOCK's speakerkit {name} table is not `kit = \"speaker\"`, so the speaker shard \
would not download it at all"
);
assert_eq!(
field(&tables[index], "local-dir"),
Some("Models/speakerkit"),
"MODELS_LOCK's speakerkit {name} table no longer stages into Models/speakerkit, so the two \
layers no longer collide — which silently retires the ordering invariant below"
);
}
assert!(
base < overlay,
"MODELS_LOCK stages the speakerkit OVERLAY (table {}) before the BASE layer (table {}), so \
the base layer's PRE-REPAIR pyannote_segmentation/wespeaker graphs would overwrite the \
fp16-guard-repaired ones the pipeline ships. The shard downloads a kit's tables in LOCK \
ORDER, so this file's order is what picks the winner.",
overlay + 1,
base + 1
);
let overlay_include =
field(&tables[overlay], "include").expect("the speakerkit overlay table has an `include`");
for bundle in ["pyannote_segmentation.mlmodelc/*", "wespeaker.mlmodelc/*"] {
assert!(
overlay_include.split(' ').any(|p| p == bundle),
"MODELS_LOCK's speakerkit overlay `include` ({overlay_include:?}) does not name {bundle:?}, \
so that shipping graph would stay the base layer's pre-repair conversion"
);
}
assert!(
!overlay_include.split(' ').any(|p| p == "*.mlmodelc/*"),
"MODELS_LOCK's speakerkit overlay `include` ({overlay_include:?}) globs every bundle, which \
stages that repo's NOT-adopted wespeaker_int8 re-conversion over the base layer's bytes"
);
assert!(
ci_contents.contains("sort | uniq -d")
&& ci_contents.contains("but this shard declares no overlay-pins"),
"ci.yml's overlay step no longer derives \"this kit stages layers\" from MODELS_LOCK's \
duplicate `local-dir`s, so a layered kit could be added with no proof of which layer won"
);
let row = matrix_rows(&ci_contents)
.into_iter()
.find(|r| row_field(r, "kit") == "speaker")
.expect("ci.yml has no `speaker` shard");
let pins = require_field(&row, "overlay-pins");
let model_io = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/speaker/model_io.rs");
let model_io =
fs::read_to_string(&model_io).unwrap_or_else(|e| panic!("read {}: {e}", model_io.display()));
let overlay_revision = field(&tables[overlay], "revision")
.expect("checked by lock_parses_and_every_table_is_complete");
let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("MODELS_LOCK.d")
.join(format!("speakerkit@{overlay_revision}.sha256"));
let manifest = fs::read_to_string(&manifest_path).unwrap_or_else(|e| {
panic!(
"read {}: {e}. The overlay table's committed manifest is what ci.yml's overlay pins are \
checked against; without it the ordering proof rests on nothing.",
manifest_path.display()
)
});
let common_mod = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/speaker/common/mod.rs");
let common_mod = fs::read_to_string(&common_mod)
.unwrap_or_else(|e| panic!("read {}: {e}", common_mod.display()));
for (bundle, gate) in [
(
"pyannote_segmentation.mlmodelc",
"fp16_safe_segmentation_matches_pinned_sha256",
),
(
"wespeaker.mlmodelc",
"fp16_safe_wespeaker_fp32_matches_pinned_sha256",
),
] {
let prefix = format!("{bundle}:");
let hash = pins
.split_whitespace()
.find_map(|pin| pin.strip_prefix(&prefix))
.unwrap_or_else(|| {
panic!(
"the speaker shard's overlay-pins carry no `{bundle}:<sha256>` entry, so a wrong \
download order would only surface later as anonymous hash drift"
)
});
assert!(
hash.len() == 64 && hash.chars().all(|c| c.is_ascii_hexdigit()),
"the speaker shard's `{bundle}` overlay pin is not a 64-hex-digit sha256: {hash:?}"
);
assert!(
model_io.contains(gate),
"tests/speaker/model_io.rs no longer defines {gate:?}, the gate ci.yml's overlay pin for \
{bundle:?} is a second copy of"
);
assert!(
manifest
.lines()
.any(|line| line.starts_with(hash) && line.ends_with(&format!("{bundle}/model.mil"))),
"ci.yml pins {bundle}/model.mil at sha256 {hash}, which {} records for no such path — the \
overlay check and the manifest the {gate} gate reads its expectations from have drifted \
apart, so CI would demand bytes that gate rejects (or accept bytes it would reject). \
Re-baseline both together.",
manifest_path.display()
);
assert!(
common_mod.contains(hash),
"ci.yml pins {bundle}/model.mil at sha256 {hash}, which appears nowhere in \
tests/speaker/common/mod.rs — `skipped_for_stale_overlay`'s OVERLAY_MODEL_MIL_PINS has \
drifted from the overlay check and the {gate} gate, so a stale-overlay skip could fire (or \
fail to fire) on bytes the other two disagree with. Re-baseline all three together."
);
}
}
#[test]
fn ci_fp16_sweep_shards_cover_every_pinned_vendor() {
let Some((lock_path, workflow_path)) = repo_files() else {
return;
};
let lock_contents = fs::read_to_string(lock_path).expect("MODELS_LOCK reads");
let tables = parse_lock(&lock_contents);
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let rows = matrix_rows(&ci_contents);
assert!(
ci_contents.contains("COREMLIT_FP16_SWEEP_VENDORS: ${{ matrix.fp16-vendors }}"),
"ci.yml's fp16 sweep step no longer takes its vendor manifest from the matrix row, so the \
per-shard coverage this test reasons about is not what CI runs"
);
let mut swept: BTreeSet<&str> = BTreeSet::new();
for row in &rows {
let kit = row_field(row, "kit");
let manifest: BTreeSet<&str> = require_field(row, "fp16-vendors").split(',').collect();
assert!(
manifest.contains("vadkit"),
"ci.yml's {kit:?} shard does not name `vadkit` in its fp16 sweep manifest. The VAD graph is \
COMMITTED, so it costs no download, and naming it in every shard is what makes deleting \
the vendored model a hard failure rather than a silent drop of the sweep's clean control."
);
let staged: BTreeSet<&str> = tables
.iter()
.filter(|t| field(t, "kit") == Some(kit))
.map(|t| vendor_of(field(t, "local-dir").expect("checked by lock_parses...")))
.collect();
for vendor in &staged {
assert!(
manifest.contains(vendor) || GRAPHLESS_VENDORS.contains(vendor),
"ci.yml's {kit:?} shard stages Models/{vendor}/ but does not sweep it \
(fp16-vendors = {:?}). Either add it, or — if that tree holds no `.mlmodelc` for the \
sweep to audit — add it to GRAPHLESS_VENDORS here with that reason.",
require_field(row, "fp16-vendors")
);
}
for vendor in &manifest {
assert!(
*vendor == "vadkit" || staged.contains(vendor),
"ci.yml's {kit:?} shard sweeps Models/{vendor}/, which that kit's MODELS_LOCK tables \
never stage ({staged:?}). The sweep's manifest is fail-closed on a missing directory, so \
this shard would fail every run."
);
}
swept.extend(manifest);
}
let unstaged: BTreeMap<&str, &str> = UNSTAGED_DEFECT_VENDORS.iter().copied().collect();
let pinned = fp16_pinned_vendors();
for vendor in &pinned {
let vendor = vendor.as_str();
if swept.contains(vendor) {
continue;
}
assert!(
unstaged.contains_key(vendor),
"tests/fp16_guards.rs pins fp16 guard findings under Models/{vendor}/ (KNOWN_DEFECTS or \
LOAD_BEARING_NORMS), but NO model-tests shard sweeps that vendor — those pins are verified \
nowhere in CI. Stage it in a shard, or record it in UNSTAGED_DEFECT_VENDORS here with the \
reason it cannot be."
);
}
for (vendor, _) in UNSTAGED_DEFECT_VENDORS {
assert!(
!swept.contains(vendor),
"UNSTAGED_DEFECT_VENDORS records Models/{vendor}/ as swept by no shard, but a shard now \
sweeps it — drop the exemption"
);
assert!(
pinned.contains(*vendor),
"UNSTAGED_DEFECT_VENDORS records Models/{vendor}/, which carries no fp16_guards pin in \
tests/fp16_guards.rs any more — drop the entry"
);
}
}
#[test]
fn ci_model_tests_gates_cannot_be_silently_skipped() {
let Some((_, workflow_path)) = repo_files() else {
return;
};
let ci_contents = fs::read_to_string(workflow_path).expect(".github/workflows/ci.yml reads");
let steps = model_tests_steps(&ci_contents);
let download = steps
.iter()
.position(|step| step.contains("id: download"))
.expect("ci.yml's model-tests job has no step with `id: download`");
let (gates, ledger) = steps[download + 1..]
.split_last()
.map(|(last, rest)| (rest, last))
.expect("ci.yml's model-tests job has no steps after the download");
assert!(
ledger.contains("name: Gate ledger") && ledger.contains("if: ${{ !cancelled() }}"),
"ci.yml's model-tests job must END with the `Gate ledger` step, guarded by `!cancelled()` \
alone so it reports even when the download died and took every check with it; its last step \
is instead:\n{ledger}"
);
for gate in [
"name: Verify staged overlay ordering",
"name: Verify staged artifact checksums",
"name: fp16 graph sweep",
"name: fp16 sweep inventory",
"name: Model gates",
] {
assert!(
gates.iter().any(|step| step.contains(gate)),
"ci.yml's model-tests job has no `{gate}` step after the download — either it was removed, \
or this test stopped parsing the job (it found {} step(s))",
gates.len()
);
}
for step in gates {
assert!(
step.contains(GATE_GUARD),
"this model-tests step does not carry `{GATE_GUARD}`, so a failure in any step before it \
marks it `skipped` and the shard reports nothing about the check that never ran:\n{step}"
);
let id = step
.lines()
.find_map(|line| line.trim().strip_prefix("id: "))
.unwrap_or_else(|| {
panic!(
"this model-tests step has no `id:`, so the `Gate ledger` step cannot \
report whether it ran:\n{step}"
)
});
let entry = format!("=${{{{ steps.{id}.outcome }}}}");
assert!(
ledger.contains(&entry),
"the `Gate ledger` step never reads step {id:?} ({entry:?}), so that check could be skipped \
without the shard saying so"
);
}
}