Run Level 2 validation probes - #1181
burtenshaw wants to merge 15 commits into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Schema worker truncates Unicode payloads
- The grader now serializes worker payloads without ASCII escaping, with a regression test covering Unicode traces within the wire budget.
Or push these changes by commenting:
@cursor push c8f6b6ffb8
Preview (c8f6b6ffb8)
diff --git a/src/openenv/validation/graders/runtime/basic.py b/src/openenv/validation/graders/runtime/basic.py
--- a/src/openenv/validation/graders/runtime/basic.py
+++ b/src/openenv/validation/graders/runtime/basic.py
@@ -143,7 +143,10 @@
try:
checked = subprocess.run(
[sys.executable, "-I", str(worker)],
- input=json.dumps({"schema": schema, "observations": observations}),
+ input=json.dumps(
+ {"schema": schema, "observations": observations},
+ ensure_ascii=False,
+ ),
capture_output=True,
text=True,
timeout=5,
diff --git a/tests/test_validation/test_runtime_grading.py b/tests/test_validation/test_runtime_grading.py
--- a/tests/test_validation/test_runtime_grading.py
+++ b/tests/test_validation/test_runtime_grading.py
@@ -219,6 +219,45 @@
)
+def test_unicode_trace_within_wire_budget_reaches_schema_worker_intact(tmp_path):
+ text = "\U0001f600" * 150_000
+ rows = []
+ for index in range(6):
+ operation = "reset" if index == 0 else "step"
+ rows.append(
+ WireExchange(
+ operation,
+ json.dumps({"type": operation}),
+ json.dumps(
+ {
+ "type": "observation",
+ "data": {
+ "observation": {"counter": index, "text": text},
+ "reward": None if index == 0 else 0,
+ "done": False,
+ },
+ },
+ ensure_ascii=False,
+ ),
+ )
+ )
+ assert (
+ sum(
+ len(row.request_json.encode()) + len(row.response_json.encode())
+ for row in rows
+ )
+ < 8 * 1024 * 1024
+ )
+ assert len(json.dumps([text] * len(rows))) > 10 * 1024 * 1024
+ schema = copy.deepcopy(OBSERVATION_SCHEMA)
+ schema["properties"]["text"] = {"type": "string"}
+ schema["required"].append("text")
+
+ result = ObservationSchemaGrader().run(subject_with(tmp_path, rows, schema=schema))
+
+ assert result.status is CheckStatus.PASS
+
+
@pytest.mark.parametrize(
"field,value", [("done", None), ("done", "false"), ("done", 0), ("observation", [])]
)You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Alignment Review Report
Two-tier review of the RFC-008 Level 2 execution slice 3 (startup, reward, observation, state checks). This is a stacked PR (base ben/rfc008-l2-02-docker), so the repo's test/lint workflows don't gate it — the results below are from running the suites locally, plus the dedicated runtime-validation CI job.
Automated Checks
- Lint: PASS (PR files).
ruff format --check,usort check, andruff checkare all clean on the 14 changed.pyfiles. The full-treelint.shfailure (57ruff format+usortexit 2) is pre-existingenvs/+test_grid_world.py/test_julia_env.pydrift, not introduced here. - Debug code: CLEAN. No
print/breakpoint/pdb/TODO/FIXMEin the PR'ssrc/files. - Tests (run locally): green. 97 new runtime unit tests pass; full
tests/test_validation= 292 passed (incl.test_policy,test_schema_sync,test_runner);tests/test_cli/test_validate.py= 11 passed (no regression — contrast slice 1). The Docker integration test (test_runtime_cli.py,@pytest.mark.docker) isn't runnable in this env, but the dedicated "Runtime validation / Linux Docker" CI job passed.
Open RFCs Context
- RFC 008 — Environment Auto-Validation (Status: In Review, @zkwentz). This PR implements the "Level 2 execution amendment", slice 3, and is faithful to it: bounded public probe loader (65 536 B / depth 32 / 10 000 nodes / 1–100 actions, duplicate-key + non-finite rejection, no
seed/episode_idoverride); a single-session collector recording raw uncoerced request/response strings; reset-reward-may-be-null / step-reward-must-be-a-finite-number-in-range-excluding-booleans; the state identity +step_countcontract; policy v2 addingruntime.startup(v1 rejected for runtime); the FAIL/ERROR/SKIP taxonomy; unsupported network/GPU refused before build; an explicit SKIP inventory for not-yet-implemented Level-2 checks; and a redacted evidence bundle. - No other RFC (001/002/003/004) is in conflict; RFC 004 rubrics remain leveraged-not-required.
Tier 1: Fixes Required
- None. No mechanical, type, import, or security defects found.
Tier 2: Alignment Discussion
Principle Conflicts
None identified. The change actively reinforces several invariants — worth affirming:
- Dual API boundary / agent isolation: the collector drives the Gym-like
reset/step/stateloop as validation orchestration (agent_boundary: api), never as an agent tool — no agent-facing exposure of simulation control. - Client–server separation: validation talks to the subject purely over the wire (
httpx/websockets); it imports nothing fromserver/. - Rewards in environment: graders only observe the environment's own reward against the declared range; no external reward computation.
- No credential exposure: strong throughout —
httpx(trust_env=False),LaunchSpecinherits no host env, artifact redaction (secret keys + token regex), failure reasons carry only exception types, and tests assert secrets never reach reports/traces. - Security depth (nice work): JSON-Schema evaluation is sandboxed in a disposable
python -Isubprocess with CPU/AS rlimits + a wall-clock deadline (ReDoS → finding, not hang); non-local$ref/$dynamicRefare rejected and the worker uses an emptyreferencing.Registry(no SSRF/file retrieval);source_digestopens filesO_NOFOLLOWand rejects symlinks.
Three small, non-blocking notes are left inline (report coherence on mid-run source change; --policy exit-code semantics; HTTP /schema vs the WebSocket-only direction).
RFC Conflicts
ALIGNMENT FLAG: Implementation landing while RFC 008 is still In Review
- Principle/RFC at stake: RFC 008 (Status: In Review) — Level 2 execution amendment
- The concern: the standing sequencing flag for this stack — the slice freezes real behavior (manifest schema v2, severity policy v2, the
validation.executionsidecar contract) against an amendment that is not yet Accepted. The implementation matches the current amendment text; the ask is to confirm those L2 contracts are settled before further slices stack on them. Non-blocking. - Suggested reviewer: @zkwentz (RFC author); @Darktex (principles/invariants owner,
git blameb1a92e1 on INVARIANTS.md)
Summary
- 0 mechanical issues to fix (Tier 1)
- 1 alignment point for human review (RFC-008 In-Review sequencing) + 3 minor non-blocking inline notes
- 0 hard RFC conflicts — the implementation is faithful to the RFC 008 Level 2 amendment
Sent by Cursor Automation: Pre-review
c92372e to
c7e8989
Compare
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
c7e89896Still blocked on inherited #1178 CI (
test_write_report_validation_report_only). Separately, Bugbot’s High finding on this head is real:
collectortreats exceptions from the finalclosesend / WebSocket context exit as collection failures (failure_reason), and runtime graders then FAIL the episode even when reset/step/state already succeeded. Teardown noise must not fail a measured episode.Also track (Medium):
- Schema worker
stdin.read(10MB)can truncate large observation batches under the 8MB trace capsocket.sendis not bound byepisode_timeout_s(only pre-checked viaremaining())Fix close-failure handling here; rebase after #1178 greens. Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Malformed envelopes crash runtime graders
- Runtime graders now convert AttributeError from non-object envelope data into a FAIL finding while preserving the collector failure reason.
Or push these changes by commenting:
@cursor push ea46021702
Preview (ea46021702)
diff --git a/src/openenv/validation/graders/runtime/basic.py b/src/openenv/validation/graders/runtime/basic.py
--- a/src/openenv/validation/graders/runtime/basic.py
+++ b/src/openenv/validation/graders/runtime/basic.py
@@ -58,7 +58,14 @@
problems.append(evidence.failure_reason)
try:
problems.extend(self.check(subject, evidence))
- except (ValueError, TypeError, KeyError, RecursionError, OverflowError):
+ except (
+ ValueError,
+ TypeError,
+ KeyError,
+ AttributeError,
+ RecursionError,
+ OverflowError,
+ ):
problems.append("malformed runtime evidence")
return CheckResult(
check_id=self.check_id,You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
2950de74Inherited #1178 CI is green now. The High teardown issue remains on this head: close/WS-exit exceptions still set
failure_reasonand fail graders after a successful reset/step/state episode.Also still open (Medium): schema-worker 10MB stdin truncate;
socket.sendnot bound byepisode_timeout_s.Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
d724b7e5(High/Medium fixed; Low residual)Fixed (prior High/Medium):
- Completed-episode teardown:
complete+ best-effort close/send no longer setsfailure_reason/ fails graders; regressions intest_runtime_transport.pycover close-send, close-failure, and genuine in-episode failure precedence.- Transport sends are bounded via
_bounded_call+ raw-socket abort againstepisode_timeout_s.- Schema worker input budget raised / wire JSON preserved (
response_json/schema_json) so unicode and compact1e9episodes within wire limits no longer truncate.Still open (Low): graders call
data.get(...)afterdata = response["data"]withoutisinstance(data, dict). The collector can store a raw exchange then raise on a non-objectdataenvelope;ObservationSchemaGrader/ state graders thenAttributeErroroutside the(ValueError, TypeError, …)catch → grader ERROR instead of contract FAIL. Guard withisinstance(data, dict)(or equivalent) before.get.Holding approval for that guard (+ exact-head CI still in flight). Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Episode timeout silently capped at 300s
- Runtime collection now honors the manifest's validated episode timeout without imposing an unrelated 300-second cap.
- ✅ Fixed: Schema worker stdin encoding can fail
- The schema worker subprocess now explicitly encodes text input as UTF-8 regardless of the parent locale.
You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
d724b7e5(Bugbot refresh)Prior High/Medium (teardown / bounded send / schema wire budget) remain fixed.
New Medium:
runner.pysilently caps collection atmin(..., 300.0)while policy bounds allowmax_episode_timeout_s=3600. Legal longer episodes can false-FAIL. Use the policy-validated declared timeout (or align the cap with the bound).Still open Lows:
- Malformed non-object
data→AttributeErrorin graders (ERROR vs FAIL).- Schema-worker stdin: parent
text=True/ensure_ascii=Falsevs worker UTF-8stdin.buffer(locale-dependent).Bugbot autofix is active — will re-review on the next head. Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
f405e4ec(autofix progress)Fixed this head:
- Medium timeout cap: collection now uses
manifest.resources.episode_timeout_sdirectly; regression asserts600.0is passed through.- Low schema-worker encoding: parent
subprocess.run(..., encoding="utf-8")aligns with UTF-8stdin.bufferreads.Still open (Low): graders still call
data.get(...)withoutisinstance(data, dict)after loading a possibly non-object envelope the collector stored before rejecting. ThatAttributeErrorescapes the grader catch set → ERROR instead of contract FAIL.Holding approval for that guard. Exact-head CI still running. Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
f405e4e to
16d6868
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Startup failures hide provider diagnostics
- Runtime startup outcomes now retain the sanitized exception details for runtime-plan, startup, and provider failures, with regression coverage for each path.
Or push these changes by commenting:
@cursor push 0577dc4585
Preview (0577dc4585)
diff --git a/src/openenv/validation/runner.py b/src/openenv/validation/runner.py
--- a/src/openenv/validation/runner.py
+++ b/src/openenv/validation/runner.py
@@ -185,27 +185,27 @@
"runtime.startup", CheckStatus.SKIP, str(exc), started=started
)
checks = []
- except RuntimePlanError:
+ except RuntimePlanError as exc:
result = _outcome(
"runtime.startup",
CheckStatus.FAIL,
- "runtime plan is missing, unsafe or invalid",
+ str(exc),
started=started,
)
checks = []
- except StartupError:
+ except StartupError as exc:
result = _outcome(
"runtime.startup",
CheckStatus.FAIL,
- "subject failed build or readiness; inspect the Docker fixture/build inputs",
+ str(exc),
started=started,
)
checks = []
- except ProviderError:
+ except ProviderError as exc:
result = _outcome(
"runtime.startup",
CheckStatus.ERROR,
- "provider could not complete a bounded operation",
+ str(exc),
started=started,
)
checks = []
diff --git a/tests/test_validation/test_runtime_execution.py b/tests/test_validation/test_runtime_execution.py
--- a/tests/test_validation/test_runtime_execution.py
+++ b/tests/test_validation/test_runtime_execution.py
@@ -7,7 +7,7 @@
import pytest
from openenv.validation.policy import load_policy, PolicyError
-from openenv.validation.providers import StartupError
+from openenv.validation.providers import ProviderError, StartupError
from openenv.validation.report import CheckResult
from openenv.validation.runner import run_validation, source_digest
from openenv.validation.runtime.artifacts import write_runtime_bundle
@@ -172,6 +172,8 @@
provider = FakeRuntimeProvider()
report = run_validation(package, max_level=Level.RUNTIME, provider=provider)
assert report.verdict.value == "fail"
+ result = next(r for r in report.results if r.check_id == "runtime.startup")
+ assert "invalid runtime plan" in result.evidence[0]
assert not provider.builds
@@ -185,9 +187,23 @@
report = run_validation(package, max_level=Level.RUNTIME, provider=provider)
result = next(r for r in report.results if r.check_id == "runtime.startup")
assert result.status is CheckStatus.FAIL
+ assert result.evidence == ["subject build failed"]
assert report.verdict.value == "fail"
+def test_provider_failure_diagnostics_are_visible(package):
+ provider = FakeRuntimeProvider()
+
+ def failed_build(*args):
+ raise ProviderError("provider deadline elapsed")
+
+ provider.build = failed_build
+ report = run_validation(package, max_level=Level.RUNTIME, provider=provider)
+ result = next(r for r in report.results if r.check_id == "runtime.startup")
+ assert result.status is CheckStatus.ERROR
+ assert result.evidence == ["provider deadline elapsed"]
+
+
@pytest.mark.parametrize("failure", [RuntimeError, KeyboardInterrupt])
def test_collector_crash_or_cancel_always_tears_down(package, monkeypatch, failure):
provider = FakeRuntimeProvider()You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
16d68689(CI green; Bugbot refresh)Prior High/Medium (teardown, bounded send, schema wire budget, timeout cap, UTF-8 encoding) remain fixed. Exact-head CI is green.
New Medium:
runtime.startupswallows sanitizedStartupError/RuntimePlanError/ProviderErrordetails into generic strings — authors lose actionable diagnostics. Surface a bounded sanitized reason.Still open (Low): malformed non-object
data→ graderAttributeError(ERROR vs FAIL).Bugbot autofix is active. Not Thursday 0.6.0 cargo.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Teardown failure hides startup findings
- Teardown failures now only mark cleanup incomplete, preserving the existing runtime startup and collection findings.
Or push these changes by commenting:
@cursor push 61b156dfda
Preview (61b156dfda)
diff --git a/src/openenv/validation/runner.py b/src/openenv/validation/runner.py
--- a/src/openenv/validation/runner.py
+++ b/src/openenv/validation/runner.py
@@ -234,12 +234,6 @@
cleanup["completed"] = True
except Exception:
cleanup["completed"] = False
- result = _outcome(
- "runtime.startup",
- CheckStatus.ERROR,
- "subject teardown failed",
- started=started,
- )
return [result, *checks], attempted, plan, evidence, inspection, cleanupYou can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at
eb7a40b1(Bugbot Medium)Prior APPROVE at this tip is withdrawn. Autofix is still in flight; do not merge until the teardown overwrite is fixed.
Tier 1
src/openenv/validation/runner.py_runtime()finally: whenrunning.stop()fails, the existingruntime.startupresult is replaced with a generic teardown ERROR. That discards prior PASS/FAIL/ERROR evidence (provider reason, collectionfailure_reason, etc.) and contradicts the helper docstring (“retain failure evidence through teardown”).Required: keep the original
result(and its evidence); record cleanup failure viacleanup["completed"] = Falseand/or append a separate note without overwriting attribution. Add a regression that fails primary startup/collection then failsstop(), asserting the primary finding remains visible.Not Thursday 0.6.0 cargo until merge.
Sent by Cursor Automation: Release
|
Rebased onto current The three latest findings are fixed in |
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
| "runtime validation requires policy v2; v1 supports --level static" | ||
| ) | ||
| signature = detect_signature(target) | ||
| digest_before = source_digest(target) |
There was a problem hiding this comment.
Initial digest errors skip the report
Medium Severity
The initial source_digest call is not on the report-preserving path. A symlink or non-regular file now raises before any checks run, so authors get an internal-error exit instead of a report. The same ValueError and OSError cases after collection are caught and recorded.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit bea5115. Configure here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at exact tip
bea5115d— Bugbot Medium (valid):Initial
source_digestskips the report path.digest_before = source_digest(target)(~L282) is uncaught. Symlink / non-regular-fileValueErrors (andOSErrorfromos.open) abort with an internal error before any checks run. Post-collectionsource_digestalready catches(ValueError, OSError)and records a reportable failure — initial digest should do the same (or otherwise emit a ValidationReport ERROR) for symmetry with the Autofix that landed inccca191e.Autofix is already kicked off — will re-review when the tip advances. Keep out of Thursday 0.6.0 cargo until green.
Sent by Cursor Automation: Release
ff538de to
65be5dd
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Failed digest still reads rejected source
- Validation now reports the digest failure without parsing, re-digesting, or running the rejected package source.
You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES still holds at
65be5dd8— prior Autofix fixed the uncaught initial digest, but Bugbot Medium is valid on the new shape:Failed digest still reads rejected source. Catching
source_digestand settingdigest_before = ""then continues intoparser.parseand_runtimebefore stampingstatic.manifestERROR. Those paths can follow the same symlinks / non-regular files the digest rejected, so host content may enter the report or build context before the ERROR is recorded.Fail closed: on initial digest
(ValueError, OSError), emit a ValidationReport with a reportable ERROR (and empty/""source_digest) and return without parse/runtime. Keep the regression that asserts no internal-error exit and no private path leakage.Autofix already kicked off — will re-review when the tip advances. Keep out of Thursday 0.6.0.
Sent by Cursor Automation: Release
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Interrupt during close drops episode
- WebSocket close teardown now swallows KeyboardInterrupt like other best-effort teardown failures, preserving completed or previously failed episode evidence.
You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Stale comment
REQUEST_CHANGES at exact tip
16240f19— Bugbot Medium (valid) after merge-main re-scan:Interrupt during post-episode websocket close drops a finished episode.
_bounded_callre-raisesKeyboardInterrupt; the teardownexcept Exceptionhandlers miss it. A completed collection then surfaces as interrupt: startup →ERROR, graders never run,failure_phasestill names the last successful op.Post-episode close/teardown should treat
KeyboardInterruptas best-effort (same as other teardown failures) oncecompleteis True, or convert toRuntimeCollectionInterruptedwithout discarding measured evidence. Autofix is already kicked off — will re-review on the new tip.Hold fold into Thursday 0.6.0 until this is green + Zach’s review.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Re-APPROVE at dd59275f (Bugbot Autofix)
Confirmed the Medium was real: post-episode KeyboardInterrupt during _bounded_call close/teardown bypassed except Exception, so a finished collection was surfaced as RuntimeCollectionInterrupted and graders never ran.
Autofix is correct and minimal — teardown except (Exception, KeyboardInterrupt) on both the graceful close send and the socket close — with a focused regression in test_runtime_transport.py. Local transport suite: 10 passed.
Still waiting on @zkwentz for the human gate before folding into Thursday 0.6.0. Exact-head CI is re-running on this tip.
Sent by Cursor Automation: Release
dd59275 to
d94f021
Compare
There was a problem hiding this comment.
Re-APPROVE at d94f0218
Autofix force-pushed the same close-interrupt fix (collector teardown catches KeyboardInterrupt; transport suite 10/10). Prior APPROVE was on superseded tip dd59275f (+1 test-line churn only).
Still waiting on @zkwentz + exact-head CI before Thursday 0.6.0 fold.
Sent by Cursor Automation: Release
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Source digest uses Unix-only flags
- Made O_NONBLOCK optional in source_digest and added a regression test for platforms where the flag is unavailable.
You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit f84f8a3. Configure here.





This PR runs local Level 2 probes, grades startup/reward/observation/state, and records replayable evidence with explicit skips for unfinished checks. Builds on #1179; third of three stacked PRs tracked in #1177.