Skip to main content

max / alloy

Bind the language whitelist across the three files that hold it Adding `c` touched the Containerfile's validator and Lang::ALL and missed build/preflight.py, which keeps its own copy. The gate caught it, but only after the recipes were written and a mint had been asked for, and the message it gave was that the builder does not offer a language the builder does offer. The three are one fact, so a test now reads all three and asserts they agree. Verified by removing `c` from preflight.py and watching it fail, because a text-matching test that silently matches nothing is worse than no test.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01WFBzMprSmNCfvdj2cGZyka
Author: Max Johnson <me@maxj.phd> · 2026-09-07 18:11 UTC
Signed with PGP, not checked
Commit: b5bbf5978b76e44d3c7f9ee4b83a443813cf79f7
Parent: 36ef051
2 files changed, +80 insertions, -1 deletion
@@ -281,7 +281,7 @@
281 281 bad += 1
282 282 print(" FAIL %s=%s is not one of %s" % (key, v, ", ".join(allowed)))
283 283 for lang in [x.strip() for x in dials.get("LANGS", "").split(",") if x.strip()]:
284 - if lang not in ("rust", "go", "python", "zig", "js"):
284 + if lang not in ("rust", "c", "go", "python", "zig", "js"):
285 285 bad += 1
286 286 print(" FAIL LANGS names %r, which the builder does not offer" % lang)
287 287 if not bad:
@@ -387,3 +387,82 @@
387 387 })
388 388 .is_some_and(|derive| derive.contains("Default"))
389 389 }
390 +
391 + /// The language whitelist lives in three files, and nothing bound them.
392 + ///
393 + /// The Containerfile validates `LANGS` against a `case` arm, `Lang::ALL` in
394 + /// image.rs is what the builder TUI offers, and build/preflight.py refuses a
395 + /// recipe naming anything else. Adding `c` on 2026-09-07 touched the first two
396 + /// and missed the third, and the gate caught it at mint time, after the recipe
397 + /// was already written and a build had been asked for.
398 + ///
399 + /// That is the cheapest possible version of this failure and it still cost a
400 + /// mint. The three are one fact and this is what makes them agree.
401 + #[test]
402 + fn the_language_whitelist_agrees_across_all_three_files() {
403 + let text = containerfile();
404 + let from_containerfile: Vec<String> = text
405 + .lines()
406 + .find_map(|line| {
407 + let line = line.trim();
408 + let body = line
409 + .strip_suffix(") ;; \\")
410 + .or_else(|| line.strip_suffix(") ;;"))?;
411 + body.contains("rust").then(|| {
412 + body.split('|')
413 + .map(|s| s.trim().to_string())
414 + .collect::<Vec<_>>()
415 + })
416 + })
417 + .expect("no LANGS case arm in the Containerfile");
418 +
419 + let image =
420 + std::fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/image.rs"))
421 + .expect("cannot read image.rs");
422 + let from_enum: Vec<String> = image
423 + .split_once("const ALL: [Lang;")
424 + .and_then(|(_, rest)| rest.split_once('['))
425 + .and_then(|(_, rest)| rest.split_once(']'))
426 + .map(|(list, _)| {
427 + list.split(',')
428 + .filter_map(|e| e.trim().strip_prefix("Lang::").map(str::to_lowercase))
429 + .collect()
430 + })
431 + .expect("no Lang::ALL in image.rs");
432 +
433 + let preflight = std::fs::read_to_string(
434 + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../build/preflight.py"),
435 + )
436 + .expect("cannot read preflight.py");
437 + let from_preflight: Vec<String> = preflight
438 + .lines()
439 + .find_map(|line| {
440 + let rest = line.trim().strip_prefix("if lang not in (")?;
441 + Some(
442 + rest.trim_end_matches("):")
443 + .split(',')
444 + .map(|s| s.trim().trim_matches('"').to_string())
445 + .filter(|s| !s.is_empty())
446 + .collect(),
447 + )
448 + })
449 + .expect("no language tuple in preflight.py");
450 +
451 + let sorted = |mut v: Vec<String>| {
452 + v.sort();
453 + v
454 + };
455 + let (a, b, c) = (
456 + sorted(from_containerfile),
457 + sorted(from_enum),
458 + sorted(from_preflight),
459 + );
460 + assert_eq!(
461 + a, b,
462 + "the Containerfile validator and Lang::ALL name different languages",
463 + );
464 + assert_eq!(
465 + b, c,
466 + "Lang::ALL and build/preflight.py name different languages",
467 + );
468 + }