Imported from cbenjamin23/moos-ivp-skills (
skills/moos-ivp-harness-builder/SKILL.md). Install upstream withnpx skills add cbenjamin23/moos-ivp-skills --skill moos-ivp-harness-builder. Copyright stays with the author.
MOOS-IvP Harness Builder
Overview
Use this skill for a harness that runs one or more self-evaluating stem missions across multiple named cases. The stem mission should own the mission grade. The harness should own case selection, patching, temp copies, port isolation, rolling parallel execution, cleanup, and direct publication of per-case result rows.
For the stem mission itself, use moos-ivp-eval-mission-builder. For ordinary
mission construction before evaluation plumbing, use moos-ivp-mission-builder.
For post-run .alog evidence, use moos-alog-analysis.
Core Rules
- Start from a stem mission that runs headlessly and writes
results.txtwith agrade=column. - Prefer placing harness directories at the repository root, alongside
missions/, for exampleharnesses/<harness_name>/paired withmissions/<stem_mission>/. In larger repositories, use optional family grouping for both sides, such asharnesses/<family>_harnesses/HNN-<harness_name>/paired withmissions/<family>_missions/<stem_mission>/. Have the harness refer to stem missions with explicit relative paths. Other layouts are acceptable when project conventions or packaging require them. - The stem mission must be a real eval mission:
pMissionEvalwrites thegrade=row. Do not accept a stem wherezlaunch.shor shell code synthesizesgrade=from target files, patch markers, or harness knowledge. - Keep case intent documented in the harness README under
CasesorCurrent Matrix. - Use exact case tokens in documentation and in
zlaunch.sh. case=is the harness-owned variation identity. Harnesses must not set, derive, require, or interpretmmod; anymmod=field produced by the mission is opaque mission-owned provenance.- Keep case setup explicit. A shell
caseblock mapping case name to patch files, fixture files, stem launch arguments, and intent is easier to audit than filename inference. - When multiple cases reuse one stem but need different setup or evaluation criteria, express the differences in the case matrix and case-owned patch files, fixture files, or stem launch arguments.
- Keep
launch.shand stem wrappers human-facing. Put loops, temp copies, aggregation, and archives in harness code. - Prefer mission-owned grades. The harness should normally prepend
case=<case_name>to the mission result row and preserve the mission'sgrade=pass|failas the case verdict. - For expected-negative cases, make the stem
pMissionEvalpass when the expected negative evidence is observed. Do not encode those cases asexpected=fail actual=failunless the harness is explicitly testing failure machinery such aspMissionEval,uMayFinish, or CLI return semantics. - Harness code should synthesize its own
grade=failrows only for runner failures, such asreason=launch_error,reason=missing_result,reason=prepare_error,reason=missing_result_file, orreason=teardown_error. - Do not add a harness-owned
reason=for ordinarypMissionEvalfailures. Preserve the mission evidence columns that explain the failure. A mission may report its own compactreason=, but the harness should not reinterpret it. - Avoid new
case_result=success|mismatch|errorresult formats for ordinary harnesses. Treat them as legacy compatibility or as a special pattern for tests whose subject is the failure machinery itself. - Keep evaluation levels strict: app-level harnesses should grade the app under test; moving/integration harnesses may grade arrival, encounter outcome, collision state, or other mission outcomes.
- Expose
--case,--port_base,--keep_workdirs,--gui,--nogui, and--max_timewhen the harness can support them. New generated harnesses must expose--jobs, default it to1, and run real backgrounded cases when it is greater than1. Prefer Bash 5.1+ rolling scheduling withwait -p <pidvar> -n, so the next pending case starts as soon as any active case finishes. Batch-barrier waves are a legacy compatibility pattern and do not satisfy the new generated harness contract. - Modern generated harnesses may require Bash 5.1+ for reliable rolling
scheduling and PID-to-case bookkeeping. Use
#!/usr/bin/env bash, add an early Bash version guard with a clear macOS/Homebrew message, and optionally re-exec a known Homebrew/Linuxbrew Bash before failing. - Treat harness
--max_timeas a run-time ceiling override forwarded to each stem eval mission'szlaunch.sh; do not use it as harness-side grading logic. - Default generated harnesses to
PORT_BASE=9000. Use higher fresh bases only as explicit run-time overrides for automation or local sessions that may collide with ordinary missions in the9000range. - Use headless mode as the default. Keep
--guiavailable for an individual case when visual inspection is useful. - For parallel execution, give each live case its own temp mission copy and port block. Do not patch or run through a shared stem directory while multiple cases are active.
- Create per-case temp mission copies under a harness-owned run root, not a
generic system temp location.
--keep_workdirsshould preserve one auditable run tree beneath the harness directory. - Use scoped teardown between cases and at harness exit. Prefer
copying
assets/moos_scoped_teardown.shinto the generated project as<project-root>/scripts/moos_scoped_teardown.sh, sourcing it from harness launchers, and callingmoos_scoped_teardown_stop_rooton the harness-owned run root or case directory. Do not use globalktm,pkill, or machine-wide cleanup as the normal path.
Workflow
- Confirm the stem mission passes as a single eval mission.
- run the eval mission static checker against the stem
launch.shaccepts and forwards--shore_mport,--veh_mport,--shore_pshare, and--veh_pshare.- launchers use
nsplug -xso.moosxand.bhvxsidecars are consumed. - generated targets prove the forwarded ports and patches actually landed.
- Define case tokens, case intent, and the mission-owned evidence each case should report.
- Document the case matrix in
README.md. - Decide whether each case needs patch files, fixture files, stem launch arguments, or no setup changes.
- Build
zlaunch.sharound:- argument parsing
- case selection and setup mapping
- optional patch overlay application
run_case- serial and rolling execution
- result aggregation
- cleanup traps
- Add the teardown helper asset to the generated project if there is not already an equivalent root-scoped helper.
- Implement port forwarding from harness to stem mission and verify generated targets reflect those ports.
- Add
--keep_workdirsfor debugging preserved temp copies. - Validate one case,
--jobs=1, then a small rolling run on a fresh--port_base.
Reference Use
- Read
references/harness-style.mdfor the overall architecture. - Read
references/case-matrix.mdbefore writing README case docs. - Read
references/nspatch-workflow.mdbefore adding patch overlays. - Read
references/ports-and-parallelism.mdbefore implementing--jobsor--port_base. - Read
references/generated-harness-self-tests.mdbefore reporting a new or heavily changed harness as trustworthy. - Read
references/validation.mdbefore reporting a harness as done. - Read
references/timing-and-benchmarking.mdbefore tuning--jobs, sleeps,--max_time, or benchmarking rolling runs. - Read
references/scoped-teardown.mdbefore writing cleanup logic. - Read
references/example-harness-zlaunch.mdfor a compact runner skeleton. - Reuse
assets/moos_scoped_teardown.shby copying it into generated harness projects as<project-root>/scripts/moos_scoped_teardown.shwhen they do not already provide an equivalent root-scoped helper. - Run
scripts/static_check_harness.sh <harness-dir>for a structural check.
Validation Checklist
- Stem mission passes alone with
./zlaunch.sh --max_time=<secs>. - Stem mission passes
moos-ivp-eval-mission-builderstatic validation; the harness static checker alone is not enough. - Harness README has a
CasesorCurrent Matrixsection with exact case tokens and prose intent. ./zlaunch.sh --case=<case> --max_time=<secs>works for at least one nominal case and one expected-negative case if the suite has both../zlaunch.sh --jobs=1 --port_base=<base>works, and a rolling run with--jobs=2or higher uses distinct temp directories and distinct port blocks. New generated harnesses should start the next pending case whenever an active case finishes, not wait for an entire batch barrier.- Aggregated results include
case=and the mission's original result columns, especiallygrade=and useful evidence fields such aseval=,warning_count=,expected=,observed=, or case-specific scalars. case=is the harness row key. Harness case setup should be explicit in the case matrix, patch files, fixture files, or stem launch arguments.form=,mhash=, and mission-owned evidence columns may be preserved as provenance.- Ordinary case success is
grade=pass. Any row withgrade!=passshould make the harness exit nonzero unless the harness is explicitly testing failure machinery. - Harness-owned failure rows use
case=<case> grade=fail reason=<runner_reason>and preserve launch return codes or setup evidence when available. - Selected runs produce one normalized result line for every selected case, including setup errors and intentional failures.
- A selected run that produces zero case rows is a harness failure and should exit nonzero with a clear diagnostic. This catches portability bugs where the case loop never actually ran.
- New generated harnesses that implement rolling scheduling should require Bash
5.1+ and check that requirement near the top of
zlaunch.sh. For legacy portable harnesses that intentionally target macOS system Bash 3.2, avoidmapfile,readarray, associative arrays,wait -n, andwait -p. --keep_workdirspreserves enough files to inspect generated targets andresults.txt.- Preserved workdirs show generated targets using distinct forwarded ports and
any intended
.moosx/.bhvxsidecars. - No harness path relies on global
ktm,pkill, orkillall. - Harness cleanup uses a root-scoped teardown helper or an equivalent recorded PID cleanup path; generated harnesses should not invent broad process cleanup.
- A teardown failure is visible, makes an otherwise successful run fail, and preserves the affected run root for inspection.
- Logs do not contain unexpected warnings hidden by case aggregation.