CLI Reference¶
The harness has six classic commands. One config, one record, one command each.
python -m harness <campaign-directory> [--dump-inputs DIR]
python -m harness sweep [--jobs N] [--match GLOB]
python -m harness regression [--environment PATH]
python -m harness coverage
python -m harness matrix [--environment PATH]
python -m harness benign --corpus <dir> [--sample N]
Under uv, prefix with uv run:
Every MCP operation is also a subcommand¶
The CLI and the MCP server expose the same operations, and
tests/test_surface_parity.py enforces it: a tool added on one side without
the other fails the build. The canonical list is harness/surface.py, and the
two surfaces call the same implementation, so a signature change moves both.
Each parameter is a flag named after the operation's parameter; a list takes a
repeatable flag, an object takes a JSON string. The result is printed as JSON,
or written to --out.
| Operation | What it does |
|---|---|
run_campaign --campaign_dir DIR |
Run one campaign (same as the directory spelling). |
run_regression [--environment_yaml PATH] |
Replay committed bypasses (same as regression). |
analyze_diff --new_text TEXT [--old_text TEXT] [--package NAME] |
Analyse whole PKGBUILD texts against a scratch database (unified diffs are rejected — pass old/new file texts, not a diff). |
validate_diff --diff_text TEXT [--forbidden_techniques JSON] |
Run the pre-TrustSight validation pipeline. |
judge_verdict --report_body JSON [--flag_threshold N] ... |
Classify a report body. |
diff_hash --diff_text TEXT |
The stable diff hash. |
list_campaigns |
Every campaign and its record summary. |
load_config --campaign_dir DIR |
Validate a campaign.yml without running it. |
get_campaign_record --campaign_name NAME |
A campaign's full record.json. |
list_campaign_traces --campaign_name NAME |
A campaign's traces. |
check_calibration |
Run the behaviour validator's calibration suite. |
get_environment_info |
The installed TrustSight and environment. |
aur_info --packages NAME [NAME ...] |
AUR metadata via the v5 RPC. |
aur_pkgbuild --package NAME |
AUR snapshot build files (PKGBUILD plus other text files). |
feed_search, feed_latest, feed_by_date, feed_by_package, feed_by_rule, feed_message, feed_stats, feed_health |
The watcher feed API. |
fp_agent_verify --verdict JSON_OR_FILE |
Cross-check an agent-proposed FP/TP verdict against deterministic, current-snapshot evidence; malformed input raises, unverifiable evidence returns verified: false. |
fp_agent_pr --result JSON_OR_FILE --target-repo trustsight\|trustsight-harness --date YYYY-MM-DD [--repo-root PATH] |
Render a verified FP/TP result as branch, title, body, and a files map of inert artifact proposals. FP results carrying code_fixes have those diffs gated (allowlist, charset, size caps) and applied with git apply in a sandboxed temp tree, so the files map ships the full post-fix sources. |
repo_file --path PATH [--max-bytes N] |
Read one watcher rule/test file (src/trustsight/ or tests/ only) for grounding FP code_fixes diffs. Read-only and size-capped. |
fp_agent_alert --result JSON_OR_FILE |
Render a verified TP result as the trustsight.confirmed-ioc alert envelope (fixed default priority). |
sweep, coverage, matrix and benign keep their classic spellings above;
the MCP server exposes them as the tools of the same name.
python -m harness <campaign-directory>¶
Runs one campaign and writes its record.
Arguments¶
| Argument | Required | Meaning |
|---|---|---|
target |
yes | A directory containing campaign.yml. |
--dump-inputs DIR |
no | Write every generated input to DIR and exit, without running. Audits what the manifest renders to. |
What happens before the first attempt¶
- The behaviour validator's calibration suite runs. A failure refuses the campaign with exit 1; no bypass number is publishable from a build that cannot tell a live chain from a dead one.
campaign.ymlis loaded and strictly validated. An unknown key is a mistake worth stopping for, not a comment.- The declared TrustSight version is compared against the installed one.
- TrustSight's data and config directories are bound to campaign-local paths.
- The database is restored, the canary is analysed, and its score is recorded.
- One API/CLI parity check runs.
Output¶
A JSON summary on stdout; campaign name, attempt count, non-zero outcomes, and the bypass rate with its interval:
{
"campaign": "known-bypasses-manual",
"attempts": 8,
"outcomes": { "behavior_lost": 3, "detected": 4, "partial_evasion": 1 },
"bypass_rate": { "estimate": 0.0, "ci_95_wilson": [0.0, 0.434482],
"denominator": "attempts reaching TrustSight",
"denominator_value": 5,
"note": "lower bound (validator is conservative)" }
}
The summary omits zero-valued outcomes; record.json keeps all of them.
Files written¶
| Path | Contents |
|---|---|
<campaign>/record.json |
The complete campaign record |
<campaign>/evidence.jsonl |
One JSON trace per attempt, with the diff embedded for bypasses |
<campaign>/inputs.yml |
The recipes as data, one cell per attempt |
<campaign>/template.PKGBUILD |
The shared skeleton, when the cells are templated |
<campaign>/env/ |
The campaign's own TrustSight data and config directories; relocate to tmpfs with HARNESS_ENV_ROOT |
<campaign>/thinking/ |
LLM reasoning logs, when the generator produces them |
fixtures-out/ |
Exported bypasses, gap fixtures and robustness finds |
python -m harness sweep¶
Runs every campaign under campaigns/, in parallel where asked. The
campaigns are independent — each binds its own database and writes only
inside its own directory — so a re-baseline is a worker pool, not a loop.
| Argument | Required | Meaning |
|---|---|---|
--jobs N | auto |
no | Concurrent campaigns (default auto, capped at 8 — RAM-bound). Each campaign still verifies its own environment and canary. |
--match GLOB |
no | Only campaigns whose name matches; repeatable. |
Each campaign still verifies its own environment and canary before its first attempt. A campaign that fails is reported in the summary and does not stop the others; the command exits 2 if any failed, 0 otherwise.
uv run python -m harness sweep --jobs auto
# {"campaigns": 74, "attempts": 1402, "bypasses": 0, "bypassing": []}
python -m harness regression¶
Replays every bypass committed by every campaign in campaigns/ against the
current environment.
Arguments¶
| Argument | Required | Meaning |
|---|---|---|
--environment PATH |
no | Environment YAML. Defaults to defaults/environment.yml. |
--jobs N | auto |
no | Replay workers (default 1; auto caps at 8). Each worker binds its own database; the merged report is identical to the serial one. |
--no-cache |
no | Ignore and do not write regression/cache.jsonl. A result is cached only when every verdict input is in the key (diff, instrument pin, config fingerprint, validator and Judge sources, canary gaps, threshold, database state). Degraded results are never cached. |
Behaviour¶
For each committed bypass hash: locate its diff by re-hashing the embedded
diff in evidence.jsonl (never by filename), restore the database, re-analyse,
and classify.
- Still UNFLAGGED → open
- A positive finding → closed, with the closing version recorded
- Fails
bash -nnow → unreplayable, with the reason - Analysis stage failed (
stage_degraded, the tokenizer sandbox timing out under load) → degraded, retried once serially, never counted closed - The tool declined to answer (any other coverage gap forbids UNFLAGGED) → inconclusive, never counted closed and never stamped with a closing version. A gap is evidence about the analysis, not about the payload, so retiring a bypass on one would be the gate being wrong in the only direction it must never be.
What is re-analysed. The gate replays the PKGBUILD the recorded verdict was
reached on, which evidence.jsonl carries in analysed_text beside the diff.
A unified diff is not the whole recipe: reconstructing a side from its hunks
drops every line outside one, so the reconstruction has no url= and no
source= — the two declarations a bundle of rules measures an undeclared fetch
against. Replaying it asks TrustSight a different question from the one the
record answers, and the two disagreed often enough to matter. A record written
before the field existed still replays, on the reconstruction, and the report's
analysed_from counts say how much of the run rests on which shape:
The gate also replays the canary and one API/CLI parity check per environment, so a "closed" bypass is never an artefact of a broken harness.
A full replay writes gigabytes
Each replay restores the database before the attempt, and a restore is
fsync-heavy. Point HARNESS_ENV_ROOT at a tmpfs to keep the churn out of
the disk (the committed tree is untouched; the scratch is keyed per work
root):
Output¶
and regression/report.json with the per-bypass detail.
This is a report, not a verdict
Improvement and regression are both data. Whether "0 open" is good news is a maintainer's call, made in a review with the report attached.
Exit codes¶
The regression gate uses 0 and 2 only. A caller scripting it should never have to distinguish "misconfigured" from "broken" to know whether a report exists. A missing environment file is therefore exit 2, not exit 1.
python -m harness coverage¶
Maps every campaign to the rules it sets out to test, against TrustSight's own
rule taxonomy, and writes coverage/report.json.
The harness is adversarial, not exhaustive. This makes the untested surface a
number: of 244 rules, how many a campaign names, grouped by category, with the
rules a cold analyze_text campaign cannot reach named and explained. Two
kinds of unreachability are separated, because they are different facts: the
adoption and composition rules need a corpus cycle or a recorded observation,
and twelve more need a committed companion file, which no campaign in this
suite can produce — every generator diffs one PKGBUILD against one baseline.
uv run python -m harness coverage
# 38 of 272 rules targeted by 103 campaigns; 234 untargeted, 23 with a reason and all 23 unreachable by this harness's format.
python -m harness matrix¶
Renders the hardness matrix: the shape archive's grid of (rule, shape)
cells for the TrustSight version in the environment file. Each cell is open
(a bypass is confirmed), closed (a patch is verified), unshaped (attempts
the catalog cannot name) or, per rule with no cells, unproven.
Writes regression/matrix.json and regression/matrix.md for the current
version, plus the frozen regression/cellmatrix-<version>.json/.md snapshot
the matrix is diffed against between releases. The diff between two versions'
matrices is the regression artifact: cells a patch closed, cells a new bypass
opened, and newly named shapes. A row for a rule the pinned TrustSight does
not ship is a configuration error, so the matrix can never claim a rule the
instrument does not have.
uv run python -m harness matrix
# 6 hardened, 11 open, 241 unproven of 258 rules; wrote regression/cellmatrix-0.19.0.json
python -m harness benign¶
Scans a directory of benign *.diff files and reports the false-positive
rate: how many a healthy update causes TrustSight to flag. This is the other
half of the measurement every campaign leaves out.
It is deliberately not a campaign. A benign update carries no fetch-to-execute
chain, so the behaviour validator would discard it as behavior_lost before
TrustSight saw it; and an unflagged benign diff is not a bypass, so the
exporter must not file it as one. This path calls trustsight.analysis.scan_diff
directly, with the host's pacman answer frozen to the cold-machine value so the
number is reproducible.
Arguments¶
| Argument | Required | Meaning |
|---|---|---|
--corpus DIR |
yes | Directory of benign *.diff files, searched recursively. |
--sample N |
no | Scan every Nth diff (default: all). Do not compare a sample's rate to a whole-corpus one. |
--environment PATH |
no | Environment YAML. Defaults to defaults/environment.yml. |
Output¶
benign/report.json, plus a one-line summary:
Against TrustSight's own 3,739-diff corpus the whole-run figure is ~7.8%, the same order as the tool's published calibration.
Exit codes¶
| Code | Meaning |
|---|---|
| 0 | The run produced a record or a report |
| 1 | A configuration or environment fault the operator must fix (campaigns only) |
| 2 | A harness error |
Whether the numbers are good news is never encoded in an exit code. See Exit Codes.