- Status: Accepted (Wave 1 landed via PR
feat/chapel-ci-strict-gates) - Date: 2026-05-30
- Refs: issue #33 (VeriSimDB hexad persistence S1–S3),
chapel/README.md,.github/workflows/chapel-ci.yml
panic-attack ships three deployment modes (see .claude/CLAUDE.md):
- Standalone — single binary, zero dependencies, USB-stick-portable.
- Panicbot — automated JSON scanning in CI (gitbot-fleet, GH Actions).
- Mass-panic — org-scale batch scanning across many repos.
Mode 3 has two layers:
src/assemblyline.rs— rayon-parallel single-machine batch scanner.chapel/— multi-machine fan-out built on Chapel locales, spawning thepanic-attackRust binary on each worker viaSubprocess.
Until this PR, the Chapel layer was prose-only: 2354 LOC of well-structured
code with no CI, no smoke test, and no contract enforcement against the
Rust binary it shells out to. Every Subprocess call assumed the Rust
side's flag set matched what MassPanic.chpl::buildCommandArgs emits;
nothing detected silent drift. The data path from RepoResult →
SystemImage → JSON had four silent losses (path, highCount,
error, categoryBreakdown all populated but never serialised by
Imaging.chpl::writeNodeJson). The README claimed a --resume /
--scheduler=static interaction that was implemented as a warning
ignored at runtime.
The Chapel layer is strictly outboard. Removing chapel/ from the
repo must leave the Rust build green and the single-machine USB-stick
experience intact. Therefore:
- No Cargo dependency on Chapel.
- No
src/references tochapel/paths. - The Rust binary gains no Chapel-specific flags; the new
describe-contractsubcommand is framed as a general orchestrator capability (useful to Nextflow / Airflow / Slurm / shell scripts / any future driver), not a Chapel bridge. - Path triggers on
.github/workflows/chapel-ci.ymlare scoped tochapel/**+ the Rust contract-defining files (src/main.rs,src/types.rs,Cargo.toml,Cargo.lock). A pure-Rust PR that doesn't touch these paths never wakes Chapel CI.
No continue-on-error anywhere. Failure on any of the six fails the PR:
chapel-parse-check—chpl --parse-onlyon every module + smoke.chapel-build—just chapel-build-ci(toolbox-free, .deb install).chapel-smoke—chapel/smoke/two_repo_smokeexercises theRepoResult → SystemImage → JSONdata flow + asserts the four silent-loss fields appear.chapel-e2e—mass-panic --numLocales=1against a synthetic 2-repo manifest. (True-nl 2requiresCHPL_COMM=gasnet; the stock .deb shipsCHPL_COMM=none. Tracked for Wave 2.)chapel-cli-contract— runspanic-attack describe-contractand asserts the live JSON matcheschapel/tests/expected_contract.json.chapel-rust-diff— rayon assemblyline vs Chapel single-locale on the same synthetic corpus; aggregates (total_weak_points,total_critical,repos_scanned) must agree.
Imaging.chpl::writeNodeJsonnow serialisespath,high_count,error,category_breakdown(with JSON-escape) — four fields that were populated in memory but dropped from the SystemImage JSON.MassPanic.chpl::selectAndAnnounceSchedulernow hard-fails when--scheduler=static --resumeis combined (previously: warning then proceeded silently).MassPanic.chpl::buildCommandArgsadjudicate mode now passes--quietfor consistency with assail / assault / ambush (was the only mode that didn't, risking banner-bleed into Chapel's JSON parser).journalEscaperenamed localout→buf— Chapel 2.8.0 parsesoutas the intent keyword more strictly than older versions.
- Subprocess hang kill path —
panic-attack --timeout=Nis currently the only safeguard; Chapelsub.wait()blocks indefinitely if the subprocess ignores the timeout. Needs a Chapel-side grace-period loop + SIGKILL. - NFS journal lock semantics — the journal directory assumes POSIX
flockworks; NFSv3 violates this. Needs a startup probe + warning. - True multi-locale CI — requires installing Chapel built with
CHPL_COMM=gasnet, which the stock.debdoesn't ship. Needs either a multilocale Chapel build step or a maintainedchapel-multilocalepackage on the runner. describe-contractSHA-pin for the .deb download — workflow currently trusts the HTTPS endpoint atchapel-lang/chapelreleases. Add SHA256 verification before promoting Chapel to a production gate.- Real BoJ-estate scheduler benchmark — README claims queue is ~5–15% slower; this is theoretical. A 350-repo, 2-locale measurement is Wave 2 work.
buildCommandArgscallsite consolidation —MassPanic.chplhas five sites that spawn the Rust binary; one (buildCommandArgs) is the canonical builder. Three other sites (runAttackPass,runAdjudicatePass,computeFingerprint) bypass it. Consolidating to a single helper is a refactor that doesn't belong in the CI PR.
Chapel's Temporal.chpl::writeTemporalHexad is the producer for
mass-panic temporal snapshots; the Rust-side hexad reader in
src/storage/mod.rs is the consumer. Issue #33's S1/S2/S3 stages
landed in the Rust side. This PR makes the producer side CI-enforced:
any change to either the Chapel writer or the Rust hexad schema that
breaks the contract is caught by chapel-rust-diff (aggregate parity)
or chapel-cli-contract (CLI shape). The four silent-loss fixes
ensure the producer no longer drops fields the hexad would otherwise
have persisted.
chapel/is now a load-bearing CI tree. New Chapel module additions must compile underchpl --parse-onlyand pass the smoke (a one-line assertion can be added per new field viachapel/smoke/two_repo_smoke.chpl).- Any clap flag rename in
src/main.rsthat affects the five Chapel-used modes (assail, assault, ambush, attack, adjudicate) will failchapel-cli-contract. The fix is to update both clap andchapel/tests/expected_contract.jsonin the same PR. - Pure-Rust PRs that don't touch the trigger paths skip chapel-ci entirely; Chapel CI failure cannot block a Rust-only release.
- Wave 2 enables real-cluster validation (
-nl 16+on actual nodes).
- Promote Chapel to a Rust dependency — rejected. Would break detachability and the USB-stick experience.
- Merge
assemblyline.rsinto Chapel — rejected. Rayon is the right tool for single-machine, Chapel for cross-machine. They are not redundant. - Use clap's
CommandFactoryreflection to auto-generate the contract fixture — adopted for thedescribe-contracthandler itself (auto-syncs with clap). Rejected for the fixture (expected_contract.json) because the fixture defines what Chapel requires, not what Rust currently offers; keeping it static documents the contract surface Chapel depends on.