verifiers 0.3.2.dev30__py3-none-any.whl → 0.3.2.dev31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- verifiers/v1/utils/artifacts.py +53 -14
- {verifiers-0.3.2.dev30.dist-info → verifiers-0.3.2.dev31.dist-info}/METADATA +1 -1
- {verifiers-0.3.2.dev30.dist-info → verifiers-0.3.2.dev31.dist-info}/RECORD +6 -6
- {verifiers-0.3.2.dev30.dist-info → verifiers-0.3.2.dev31.dist-info}/WHEEL +0 -0
- {verifiers-0.3.2.dev30.dist-info → verifiers-0.3.2.dev31.dist-info}/entry_points.txt +0 -0
- {verifiers-0.3.2.dev30.dist-info → verifiers-0.3.2.dev31.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/utils/artifacts.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Bounded, validated artifact collection and restoration across runtimes."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import io
|
|
5
6
|
import logging
|
|
6
7
|
import shlex
|
|
8
|
+
import tarfile
|
|
7
9
|
import uuid
|
|
8
10
|
from pathlib import PurePosixPath
|
|
9
11
|
from typing import TYPE_CHECKING
|
|
@@ -110,7 +112,12 @@ async def collect(
|
|
|
110
112
|
|
|
111
113
|
|
|
112
114
|
async def restore(runtime: Runtime, collected: dict[str, bytes | None]) -> None:
|
|
113
|
-
"""Extract `collected` in `runtime` at the original absolute paths.
|
|
115
|
+
"""Extract `collected` in `runtime` at the original absolute paths.
|
|
116
|
+
|
|
117
|
+
Archive bytes are untrusted: the agent controls both their source files and the
|
|
118
|
+
runtime tooling that creates them. Every archive is therefore validated on the host
|
|
119
|
+
before the grading runtime is changed. Only regular files and directories travel.
|
|
120
|
+
"""
|
|
114
121
|
if not collected:
|
|
115
122
|
return
|
|
116
123
|
# Restoring into the subprocess runtime would extract absolute paths onto the
|
|
@@ -120,6 +127,8 @@ async def restore(runtime: Runtime, collected: dict[str, bytes | None]) -> None:
|
|
|
120
127
|
"refusing to restore artifacts into the subprocess runtime: extraction "
|
|
121
128
|
"writes to absolute paths on the host. Grade in a container."
|
|
122
129
|
)
|
|
130
|
+
for root, archive in collected.items():
|
|
131
|
+
_validate_restore(root, archive)
|
|
123
132
|
# Clear every root up front, not per entry: a later nested root would otherwise
|
|
124
133
|
# delete content an earlier one just restored. Clearing also drops any file or
|
|
125
134
|
# symlink the image left at the target.
|
|
@@ -141,23 +150,16 @@ async def _tar_out(runtime: Runtime, artifact: Artifact, budget: int) -> bytes:
|
|
|
141
150
|
path = f"/tmp/vf-artifact-{uuid.uuid4().hex}.tar"
|
|
142
151
|
excludes = " ".join(f"--exclude={shlex.quote(p)}" for p in artifact.exclude)
|
|
143
152
|
try:
|
|
153
|
+
# macOS tar otherwise adds AppleDouble sidecars next to a directory root.
|
|
144
154
|
await _run(
|
|
145
155
|
runtime,
|
|
146
|
-
f"tar -cf {shlex.quote(path)} -C / {excludes} -- "
|
|
156
|
+
f"COPYFILE_DISABLE=1 tar -cf {shlex.quote(path)} -C / {excludes} -- "
|
|
147
157
|
f"{shlex.quote(artifact.source.lstrip('/'))}",
|
|
148
158
|
f"collect artifact {artifact.source!r}",
|
|
149
159
|
)
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
if (raw := sized.stdout.strip()).isdigit() and int(raw) > budget:
|
|
154
|
-
raise RuntimeError(
|
|
155
|
-
f"artifact {artifact.source!r} takes the collection over the "
|
|
156
|
-
f"{MAX_ARTIFACT_BYTES} byte limit. The grading box boots from the "
|
|
157
|
-
"agent's image, so only the delta needs to travel — narrow the source "
|
|
158
|
-
"or add `exclude` patterns."
|
|
159
|
-
)
|
|
160
|
-
return await runtime.read(path)
|
|
160
|
+
# The runtime enforces the remaining collection budget while transferring the
|
|
161
|
+
# bytes, so replacing or growing the archive cannot race a separate size probe.
|
|
162
|
+
return await runtime.read(path, max_bytes=budget)
|
|
161
163
|
finally:
|
|
162
164
|
# Best-effort: the box is about to be destroyed and the name is unique per call.
|
|
163
165
|
try:
|
|
@@ -166,6 +168,43 @@ async def _tar_out(runtime: Runtime, artifact: Artifact, budget: int) -> bytes:
|
|
|
166
168
|
logger.debug("failed to remove %s", path, exc_info=True)
|
|
167
169
|
|
|
168
170
|
|
|
171
|
+
def _validate_restore(root: str, archive: bytes | None) -> None:
|
|
172
|
+
"""Reject archive content that could restore outside its declared root."""
|
|
173
|
+
root_path = PurePosixPath(root)
|
|
174
|
+
if (
|
|
175
|
+
root_path.anchor != "/"
|
|
176
|
+
or ".." in root_path.parts
|
|
177
|
+
or root_path == PurePosixPath("/")
|
|
178
|
+
):
|
|
179
|
+
raise RuntimeError(
|
|
180
|
+
f"artifact root {root!r} must be an absolute path below '/' with no '..'"
|
|
181
|
+
)
|
|
182
|
+
if archive is None:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as tar:
|
|
187
|
+
for member in tar:
|
|
188
|
+
member_path = PurePosixPath(member.name)
|
|
189
|
+
destination = PurePosixPath("/") / member_path
|
|
190
|
+
if (
|
|
191
|
+
not member.name
|
|
192
|
+
or member_path.is_absolute()
|
|
193
|
+
or ".." in member_path.parts
|
|
194
|
+
or not destination.is_relative_to(root_path)
|
|
195
|
+
):
|
|
196
|
+
raise RuntimeError(
|
|
197
|
+
f"artifact member {member.name!r} is outside declared root "
|
|
198
|
+
f"{root!r}"
|
|
199
|
+
)
|
|
200
|
+
if not (member.isfile() or member.isdir()):
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
f"artifact member {member.name!r} is a link or special file"
|
|
203
|
+
)
|
|
204
|
+
except tarfile.TarError as exc:
|
|
205
|
+
raise RuntimeError(f"unreadable artifact archive for {root!r}: {exc}") from exc
|
|
206
|
+
|
|
207
|
+
|
|
169
208
|
async def _run(runtime: Runtime, command: str, action: str) -> None:
|
|
170
209
|
result = await runtime.run(["sh", "-c", command], {})
|
|
171
210
|
if result.exit_code:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifiers
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev31
|
|
4
4
|
Summary: Verifiers: Environments for LLM Reinforcement Learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
|
|
6
6
|
Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
|
|
@@ -168,7 +168,7 @@ verifiers/v1/tasksets/textarena/__init__.py,sha256=Os2OlBY_pSH0B1DP32RitumgSLpq2
|
|
|
168
168
|
verifiers/v1/tasksets/textarena/taskset.py,sha256=9fa_unlFiFZyuN4I02rW3x4qyRnFWXqwTUZTloQefu0,4433
|
|
169
169
|
verifiers/v1/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
170
170
|
verifiers/v1/utils/aio.py,sha256=0yNFOqB2oO6Qktc6NI_ZSAGuo1aHsIK_wuT1BtmoZ3I,1532
|
|
171
|
-
verifiers/v1/utils/artifacts.py,sha256=
|
|
171
|
+
verifiers/v1/utils/artifacts.py,sha256=GowZubXGk6NhVgaAUKCVXYoBh_3L1Aw0LAHe6tNwl8g,8447
|
|
172
172
|
verifiers/v1/utils/compile.py,sha256=5pxy31wx2Q-3pQjOO5B9SH6OT-wwb8r4F1LAl6HMeQA,5835
|
|
173
173
|
verifiers/v1/utils/decorators.py,sha256=vhoN6YoKb15-Fkqmc9RaNlUIbt9WRvNK4zAn2bAxfS4,5367
|
|
174
174
|
verifiers/v1/utils/format.py,sha256=NfQo9M5KMzWCZpQaet5YtsJx56vCKAZPfbjZZJLJhhM,2187
|
|
@@ -185,8 +185,8 @@ verifiers/v1/utils/prime.py,sha256=UTYRjp9cbjNb6CVmBHda-1wWZAIIfxNOmTyNuT7_wL4,9
|
|
|
185
185
|
verifiers/v1/utils/retries.py,sha256=Y2ZgrAjn-qNkRKZP_RVNL_7EK0iaRcZeCa404lpGYi4,5417
|
|
186
186
|
verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
|
|
187
187
|
verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
|
|
188
|
-
verifiers-0.3.2.
|
|
189
|
-
verifiers-0.3.2.
|
|
190
|
-
verifiers-0.3.2.
|
|
191
|
-
verifiers-0.3.2.
|
|
192
|
-
verifiers-0.3.2.
|
|
188
|
+
verifiers-0.3.2.dev31.dist-info/METADATA,sha256=0h0tNHbRYVLoDa0whEvfRNXDH9SkdX41bAn-Ct2aC2A,4144
|
|
189
|
+
verifiers-0.3.2.dev31.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
190
|
+
verifiers-0.3.2.dev31.dist-info/entry_points.txt,sha256=uqQje0TMsr7k6nXlWxlPs13Si0evb3pVCEDF6c-YL80,241
|
|
191
|
+
verifiers-0.3.2.dev31.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
|
|
192
|
+
verifiers-0.3.2.dev31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|