Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 63 additions & 37 deletions crates/proof-vm-guest/tests/bake_tooling.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! The operator bake tooling under `deploy/guest/` stays runnable and
//! generalist: scripts parse, the bake plans without root and refuses what
//! it must, rootless podman is pointed at paths init makes writable for the
//! run-as user, and no harness is named anywhere under `deploy/guest/`.
//! Nothing here builds an image or runs a container.
//! The operator bake tooling under `deploy/guest/` stays runnable: scripts
//! parse, the bake plans without root and refuses what it must, rootless
//! podman is pointed at paths init makes writable for the run-as user.
//! Generic guest scripts (`bake-rootfs.sh`, `init.sh`, `agent-loop.sh`) do
//! not name a harness. A versioned reference adaptor may ship under
//! `deploy/guest/runners/<id>/`. Nothing here builds an image or runs a
//! container.

#![allow(clippy::expect_used, clippy::unwrap_used, clippy::too_many_lines)]

Expand Down Expand Up @@ -50,6 +52,24 @@ fn guest_scripts_parse() {
("bash", "deploy/guest/bake-rootfs.sh"),
("sh", "deploy/guest/init.sh"),
("sh", "deploy/guest/agent-loop.sh"),
("bash", "deploy/guest/runners/rlm_fc_in_guest_harbor/run"),
(
"bash",
"deploy/guest/runners/rlm_fc_in_guest_harbor/inspect",
),
("bash", "deploy/guest/runners/rlm_fc_in_guest_harbor/lib.sh"),
(
"bash",
"deploy/guest/runners/rlm_fc_in_guest_harbor/harness/run-harbor",
),
(
"bash",
"deploy/guest/runners/rlm_fc_in_guest_harbor/tests/test_adaptor.sh",
),
(
"bash",
"deploy/guest/runners/rlm_fc_in_guest_harbor/tests/run.sh",
),
] {
let status = Command::new(shell)
.arg("-n")
Expand Down Expand Up @@ -327,38 +347,19 @@ fn podman_storage_points_at_paths_init_makes_writable_for_the_runner() {
let _ = std::fs::remove_dir_all(&d);
}

/// Zero challenge content in git: nothing under `deploy/guest/` names a
/// harness, a benchmark, or a task set. Runner ids, packs, harness CLIs,
/// agents, and scoring rules are operator artefacts staged outside this
/// repository and selected by signed topic params.
/// Generic bake/init/agent-loop scripts stay generalist: they do not name a
/// harness. A versioned **reference adaptor** may ship under
/// `deploy/guest/runners/<id>/` so operators can bake it; it is not compiled
/// into any Proof binary. Challenge task lists still do not belong in git.
#[test]
fn deploy_guest_names_no_harness_or_benchmark() {
let mut files = Vec::new();
let mut stack = vec![repo().join("deploy/guest")];
while let Some(d) = stack.pop() {
for e in std::fs::read_dir(&d).expect("read_dir").flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
} else {
files.push(p);
}
}
}
assert!(files.len() >= 4, "{files:?}");
let runners: Vec<_> = files
.iter()
.filter(|p| p.to_string_lossy().contains("/runners/"))
.collect();
assert_eq!(
runners.len(),
1,
"only the contract README ships under runners/: {runners:?}"
);
for f in &files {
let lower = std::fs::read_to_string(f)
.expect("text file")
.to_ascii_lowercase();
let generic = [
"deploy/guest/bake-rootfs.sh",
"deploy/guest/init.sh",
"deploy/guest/agent-loop.sh",
];
for rel in generic {
let lower = read(rel).to_ascii_lowercase();
for forbidden in [
"harbor",
"tb4",
Expand All @@ -371,9 +372,34 @@ fn deploy_guest_names_no_harness_or_benchmark() {
] {
assert!(
!lower.contains(forbidden),
"{} names {forbidden:?}; harness content is operator content, never in git",
f.display()
"{rel} names {forbidden:?}; generic guest scripts stay harness-agnostic"
);
}
}

let adaptor = repo().join("deploy/guest/runners/rlm_fc_in_guest_harbor");
for required in [
"run",
"inspect",
"README.md",
"harness/run-harbor",
"harness/summarize.py",
] {
let p = adaptor.join(required);
assert!(p.is_file(), "reference adaptor missing {}", p.display());
}
assert!(
adaptor.join("run").metadata().unwrap().permissions().mode() & 0o111 != 0,
"reference adaptor run must be executable"
);
}

/// The Harbor reference adaptor's unit tests (no Harbor CLI, no podman).
#[test]
fn rlm_fc_in_guest_harbor_adaptor_tests() {
let status = Command::new("bash")
.arg(repo().join("deploy/guest/runners/rlm_fc_in_guest_harbor/tests/run.sh"))
.status()
.expect("run adaptor tests");
assert!(status.success(), "rlm_fc_in_guest_harbor tests failed");
}
5 changes: 3 additions & 2 deletions deploy/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ failed / unconfirmed → 503, no row). The guest image is baked with
run-as-owned scratch paths + `proof-vm-guest-agent` + operator adaptors per
[`guest/runners/README.md`](guest/runners/README.md), harness tooling via the
generic `--extra-pkgs` / `--overlay` / `--chroot-hook`) and named after its
own `sha256sum`; packs, adaptors, and harnesses are operator artefacts,
never in git (the repo ships the contract and a fail-closed skeleton only).
own `sha256sum`; packs and the Harbor CLI/venv are operator artefacts. The
adaptor contract plus the versioned Harbor evaluate reference live under
[`guest/runners/`](guest/runners/README.md).
Procedure, RE-LOCK, and limits:
[`docs/runbooks/proof-experiment-vms.md`](../docs/runbooks/proof-experiment-vms.md).

Expand Down
21 changes: 12 additions & 9 deletions deploy/guest/runners/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ image by the operator (`bake-rootfs.sh --runner <id>=<dir>`) under
`/opt/proof/runners/<id>/`, where `<id>` is exactly the value the topic puts
in `constraints.params.in_guest_benchmark_runner` (alias `baseline_runner`;
shape `[a-z0-9][a-z0-9_-]{1,63}`). No adaptor is compiled into any Proof
binary and **none ships in this repository**: a topic that names an id this
image does not carry fails closed (`RlmToHost::Failed` → 503, no row).

This directory holds the **contract only**. The harness an adaptor drives —
its CLI, its agent, its task format, how a trial's output becomes a number —
is operator content that lives outside git (baked with `--overlay` /
`--chroot-hook` / `--extra-pkgs`, or shipped inside the topic-pinned pack) and
is recognised by nothing in this repository. Proof stays generalist: the
words below are generic knobs; every value is topic data.
binary. A topic that names an id this image does not carry fails closed
(`RlmToHost::Failed` → 503, no row). One **versioned reference adaptor**
ships under [`rlm_fc_in_guest_harbor/`](rlm_fc_in_guest_harbor/) so operators
can bake Harbor evaluate with miner artefact attach; it is still selected
only when a signed topic names that runner id.

This directory holds the **contract** and, when a live gap needs a bakeable
fix, a reference adaptor directory named after the runner id. The Harbor
CLI, its venv, and the task pack stay operator content (`--overlay` /
`--chroot-hook` / `--extra-pkgs`, or the topic-pinned pack). Proof binaries
stay generalist: the words below are generic knobs; every value is topic
data.

The contract lives in `crates/proof-vm-guest/src/runner.rs`; this is the
operator's view of it.
Expand Down
203 changes: 203 additions & 0 deletions deploy/guest/runners/rlm_fc_in_guest_harbor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Runner `rlm_fc_in_guest_harbor`

Versioned **reference adaptor** for in-guest Harbor evaluate. It is operator
bake content, not compiled into any Proof binary. A signed topic selects it
only by putting this exact id in `constraints.params.baseline_runner`
(alias `in_guest_benchmark_runner`). Any other id still fails closed until
that id is baked.

This tree exists because the live overlay
`/var/lib/proof/overlays/harbor-harness/opt/proof/harness/run-harbor` (and the
thin adaptor that `exec`'d it) **ignored `$PROOF_ARTIFACT_DIR`** on evaluate
(`harbor run … -a terminus-2` on the operator pack) and always loaded the
**owner** key from `PROOF_SECRETS_DIR`. Inspect already saw the artefact;
evaluate did not use the miner code. That gap is the bug this adaptor closes.

## What Harbor `-a` actually accepts

Verified against Harbor CLI (`-a` / `--agent`) and `AgentFactory`:

| Value | Meaning |
|-------|---------|
| Built-in name | `terminus-2`, `oracle`, `claude-code`, … |
| Python import path | `module.path:ClassName` (subclass of `BaseAgent` / `BaseInstalledAgent`) |
| ACP shorthand | `acp:opencode@…` |

Harbor **does not** accept a filesystem path for `-a`. This adaptor therefore
**does not** pass `-a $PROOF_ARTIFACT_DIR/agent`. On evaluate it:

1. Detects a Harbor agent directory in the unpacked artefact (AST scan, no
miner code executed at resolve time).
2. Puts the parent of that directory on `PYTHONPATH`.
3. Passes `-a module.path:ClassName` so Harbor imports the miner's class.

A one-line `import_path` file inside the agent dir (contents
`module.path:ClassName`) wins when several classes exist. A built-in name in
that file is refused: that would ignore miner code the same way `terminus-2`
did.

## Agent selection (miner attach surface)

| Job | Artefact | What `-a` is |
|-----|----------|----------------|
| `evaluate` | `$PROOF_ARTIFACT_DIR/agent/` is a Harbor agent dir | that import path |
| `evaluate` | else `$PROOF_ARTIFACT_DIR/recipe/agent/` | that import path |
| `evaluate` | `$PROOF_ARTIFACT_DIR/recipe/run.sh` only | **fail closed** — classic recipe marker is not a Harbor `-a` target; do not wrap it as `terminus-2` |
| `evaluate` | artefact staged, neither agent dir | **fail closed** — never the topic agent |
| `baseline` | `PROOF_ARTIFACT_DIR` unset | topic `PROOF_PARAM_HARBOR_AGENT` (operator pack agent, e.g. `terminus-2`) |
| `baseline` | artefact has a Harbor agent dir | that import path (same resolution) |
| `baseline` | artefact staged but no agent dir | **fail closed** — topic fallback is only when no miner artefact is staged |

## Miner artefact (uncompressed tar)

Identity is the served file, verbatim (`tar -cf`, then `sha256sum` that
file). After unpack, paths are relative to `$PROOF_ARTIFACT_DIR`.

**Preferred** (matches `tar -cf recipe.tar recipe/`):

```
recipe/
agent/ # Harbor agent this adaptor passes to `harbor run -a`
agent.py # class …(BaseAgent) or BaseInstalledAgent
import_path # optional: one line `agent.agent:ClassName`
run.sh # optional classic marker; inspect may see it; evaluate does not exec it
README.md
```

**Tar root** if you pack with `tar -cf recipe.tar -C recipe .`:

```
agent/ # same Harbor agent dir, at the unpack root
recipe/run.sh # optional
README.md
```

Resolution order after unpack: `$PROOF_ARTIFACT_DIR/agent` then
`$PROOF_ARTIFACT_DIR/recipe/agent`. Be explicit which layout you hash and
serve; re-tarring changes the digest.

Off-limits in the artefact (inspect fails the named rule):

- `no_eval_short_circuit` (and `skip_eval` / `skip_verifier` / `always_pass_eval` / `short_circuit_eval`)
- `no_tb4_hardcoding` (and `tb4_answers` / `hardcoded_tb4`)

Do not quote those markers in miner code or README inside the tar.

## BYOK

If the topic sets `constraints.params.miner_byok` (`PROOF_PARAM_MINER_BYOK`):

- **evaluate:** export that variable from
`$PROOF_MINER_ENV_DIR/$PROOF_PARAM_MINER_BYOK` (`0600`). Missing file →
fail closed. **No** owner `PROOF_SECRETS_DIR` fallback.
- **baseline:** use the miner file when it is staged; otherwise the owner
key `PROOF_SECRETS_DIR/$PROOF_PARAM_INFERENCE_KEY_FILE` into
`PROOF_PARAM_INFERENCE_KEY_ENV` (operator-paid reference run).

Never print key material. The guest agent also redacts log tails.

## Environment (adaptor inputs)

From the guest contract (`deploy/guest/runners/README.md`): `PROOF_JOB`,
`PROOF_PACK_DIR`, `PROOF_ARTIFACT_DIR`, `PROOF_OUTPUT_DIR`, `PROOF_WORK_DIR`,
`PROOF_SEED`, `PROOF_MODEL_PIN`, `PROOF_TASK_SLICE`, `PROOF_PARAM_*`,
`PROOF_MINER_ENV_DIR` / `PROOF_MINER_ENV_NAMES`, `PROOF_SECRETS_DIR`.

Topic params this adaptor reads (all optional except `tasks_dir`; never
hardcoded to a benchmark name):

| Param | Env | Role |
|-------|------|------|
| `tasks_dir` | `PROOF_PARAM_TASKS_DIR` | Relative path under the pack. Refused if absolute or contains `..` |
| `harbor_agent` | `PROOF_PARAM_HARBOR_AGENT` | Topic built-in for **baseline only** when no artefact agent |
| `miner_byok` | `PROOF_PARAM_MINER_BYOK` | Miner key name |
| `inference_key_file` / `inference_key_env` | owner key for baseline when miner_byok is unused |
| `n_concurrent` / `n_attempts` | Harbor `--n-concurrent` / `--n-attempts` (default `1`) |
| `harbor_environment` | `PROOF_PARAM_HARBOR_ENVIRONMENT` | Passed as Harbor `--env` (e.g. `docker` / `podman`) when set |

Tasks stay the operator pack. The miner attach surface is the **agent**, not
the task list.

## Outputs

`run` writes `$PROOF_OUTPUT_DIR/report.json`:

```json
{
"primary_value": 0.73,
"claim_holds": true,
"evidence": {
"trials": [{"name": "task__1", "reward": 1.0}],
"n_measured": 1,
"mean_reward": 0.73,
"harbor_run_tail": "…",
"agent": "agent.agent:MinerAgent",
"agent_source": "artifact_dir/recipe/agent"
}
}
```

`primary_value` is the mean of Harbor trial `verifier_result.rewards.reward`
only. No measured trial → fail closed, no invented number.

`inspect` writes `$PROOF_OUTPUT_DIR/checklist.json` (no Harbor, no keys).

## Operator bake / deploy

Harbor itself stays an operator overlay (venv / `--chroot-hook`). This
directory is the adaptor + summarize harness.

**Preferred — bake the adaptor into the guest image:**

```bash
deploy/guest/bake-rootfs.sh \
--guest-agent target/x86_64-unknown-linux-musl/release/proof-vm-guest-agent \
--runner rlm_fc_in_guest_harbor="$(pwd)/deploy/guest/runners/rlm_fc_in_guest_harbor" \
--overlay /path/outside/git/harbor-venv-overlay \
--chroot-hook /path/outside/git/install-harbor.sh \
--resolver <allowlisted resolver> \
--out-dir ./out
```

`--runner` copies this whole tree to `/opt/proof/runners/rlm_fc_in_guest_harbor/`.
`run` execs `harness/run-harbor` next to it. Do **not** keep the old overlay
script at `/opt/proof/harness/run-harbor` as the evaluate path.

**Metal copy (no re-bake), matching the live runners dir:**

```bash
install -d -m 0755 /var/lib/proof/runners/rlm_fc_in_guest_harbor
cp -a deploy/guest/runners/rlm_fc_in_guest_harbor/. \
/var/lib/proof/runners/rlm_fc_in_guest_harbor/
chmod 0755 /var/lib/proof/runners/rlm_fc_in_guest_harbor/run \
/var/lib/proof/runners/rlm_fc_in_guest_harbor/inspect \
/var/lib/proof/runners/rlm_fc_in_guest_harbor/harness/run-harbor
```

Then re-bake or remount so the guest image actually contains those files.
Copying onto the KVM host overlay is not enough unless the guest rootfs
includes them.

Optional overlay layout if you still ship harness files under
`/opt/proof/harness/`: copy `harness/run-harbor` + `summarize.py` there
**and** point the adaptor `run` at them only after this tree's evaluate
behavior is in that copy. The in-tree `run` does not exec the old overlay.

`tests/` is CI-only; omit it on metal if you want a smaller copy.

Topic params must name this runner id and a relative `tasks_dir` inside the
pinned pack. Re-pin `experiment_pack_digest` when the pack tar bytes change.

## Guest runtime (read-only rootfs)

- `TMPDIR` / `TMP` / `TEMP` → `$PROOF_WORK_DIR/tmp` (`/var/tmp` is often not
writable on the RO rootfs).
- Rootless `podman system service` + `DOCKER_HOST` so Harbor's default Docker
environment talks to the guest's podman API socket (same pattern as
`deploy/guest/runners/README.md`).
- `PROOF_HARNESS_SKIP_PODMAN=1` skips that socket (unit tests).

## Miner-facing guide

Attach layout and BYOK: [`docs/external-miner/proof-tbench.md`](../../../../docs/external-miner/proof-tbench.md).
Generic contract: [`../README.md`](../README.md).
Loading
Loading