verifiers 0.4.2.dev27__tar.gz → 0.4.2.dev29__tar.gz

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.
Files changed (271) hide show
  1. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/PKG-INFO +1 -1
  2. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_taskset.py +10 -0
  3. verifiers-0.4.2.dev29/tests/unit/utils/test_artifacts.py +114 -0
  4. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/select.py +29 -1
  5. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/harbor/taskset.py +5 -1
  6. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/artifacts.py +52 -0
  7. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/.gitignore +0 -0
  8. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/LICENSE +0 -0
  9. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/README.md +0 -0
  10. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/pyproject.toml +0 -0
  11. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/conftest.py +0 -0
  12. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/agent_user.py +0 -0
  13. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/agent_user_harness.py +0 -0
  14. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/counter_tool.py +0 -0
  15. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/duet.py +0 -0
  16. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/echo.py +0 -0
  17. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/echo_acp_resume.py +0 -0
  18. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/echo_agentic.py +0 -0
  19. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/echo_tool.py +0 -0
  20. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/echo_user_sim.py +0 -0
  21. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/fetch_tool.py +0 -0
  22. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/network_policy.py +0 -0
  23. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/parallel.py +0 -0
  24. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/pyproject.toml +0 -0
  25. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/skill_codeword.py +0 -0
  26. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/skills/project-codeword/SKILL.md +0 -0
  27. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/tool_error.py +0 -0
  28. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/fixtures/tool_response_image.py +0 -0
  29. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/grid.py +0 -0
  30. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_agent_user.py +0 -0
  31. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_agentic_judge.py +0 -0
  32. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_examples.py +0 -0
  33. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_limits.py +0 -0
  34. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_mounts.py +0 -0
  35. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_multi_agent.py +0 -0
  36. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_multi_turn.py +0 -0
  37. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_multi_turn_tool.py +0 -0
  38. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_multimodal_tool.py +0 -0
  39. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_native_compose.py +0 -0
  40. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_network_policy.py +0 -0
  41. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_parallel.py +0 -0
  42. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_resume_tool.py +0 -0
  43. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_rubric_judge.py +0 -0
  44. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_sampling.py +0 -0
  45. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_scoring_network.py +0 -0
  46. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_service_scoring.py +0 -0
  47. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_shared_tool.py +0 -0
  48. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_single_turn.py +0 -0
  49. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_skills.py +0 -0
  50. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_stateful_tool.py +0 -0
  51. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_tool_vms.py +0 -0
  52. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_user_echo.py +0 -0
  53. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/integration/test_user_sim.py +0 -0
  54. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/conftest.py +0 -0
  55. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/harnesses/test_core.py +0 -0
  56. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/harnesses/test_rlm.py +0 -0
  57. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/interception/test_tunnel.py +0 -0
  58. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/judges/conftest.py +0 -0
  59. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/judges/test_judge.py +0 -0
  60. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/judges/test_reference.py +0 -0
  61. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/judges/test_rubric.py +0 -0
  62. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/runtimes/test_private_network.py +0 -0
  63. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_agent.py +0 -0
  64. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_api.py +0 -0
  65. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_best_of_n_judge.py +0 -0
  66. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_delta.py +0 -0
  67. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_dialects.py +0 -0
  68. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_graph.py +0 -0
  69. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_harbor_grading_network.py +0 -0
  70. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_retry_ids.py +0 -0
  71. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_runtimes.py +0 -0
  72. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_scoring_network.py +0 -0
  73. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_semantic.py +0 -0
  74. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_task.py +0 -0
  75. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_token_arrays.py +0 -0
  76. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/test_trace.py +0 -0
  77. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/utils/test_compile.py +0 -0
  78. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/utils/test_loaders.py +0 -0
  79. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/tests/unit/utils/test_score.py +0 -0
  80. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/__init__.py +0 -0
  81. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/__init__.py +0 -0
  82. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/acp/__init__.py +0 -0
  83. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/acp/runner.py +0 -0
  84. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/agent.py +0 -0
  85. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/__init__.py +0 -0
  86. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/debug.py +0 -0
  87. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/gepa.py +0 -0
  88. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/output.py +0 -0
  89. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/resolve.py +0 -0
  90. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/resume.py +0 -0
  91. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/run.py +0 -0
  92. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/cli/validate.py +0 -0
  93. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/clients/__init__.py +0 -0
  94. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/clients/base.py +0 -0
  95. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/clients/client.py +0 -0
  96. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/clients/eval.py +0 -0
  97. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/clients/train.py +0 -0
  98. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/__init__.py +0 -0
  99. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/agent.py +0 -0
  100. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/cli/__init__.py +0 -0
  101. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/cli/debug.py +0 -0
  102. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/cli/env.py +0 -0
  103. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/cli/run.py +0 -0
  104. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/cli/validate.py +0 -0
  105. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/client.py +0 -0
  106. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/deployment.py +0 -0
  107. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/env.py +0 -0
  108. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/harness.py +0 -0
  109. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/interception.py +0 -0
  110. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/judge.py +0 -0
  111. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/retries.py +0 -0
  112. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/runtime.py +0 -0
  113. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/sampling.py +0 -0
  114. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/scoring.py +0 -0
  115. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/task.py +0 -0
  116. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/configs/taskset.py +0 -0
  117. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/delta.py +0 -0
  118. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/dialects/__init__.py +0 -0
  119. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/dialects/anthropic.py +0 -0
  120. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/dialects/base.py +0 -0
  121. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/dialects/chat.py +0 -0
  122. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/dialects/responses.py +0 -0
  123. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/env.py +0 -0
  124. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/__init__.py +0 -0
  125. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/agentic_judge/__init__.py +0 -0
  126. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/agentic_judge/env.py +0 -0
  127. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/best_of_n/__init__.py +0 -0
  128. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/best_of_n/env.py +0 -0
  129. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/best_of_n_judge/__init__.py +0 -0
  130. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/best_of_n_judge/env.py +0 -0
  131. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/single_agent/__init__.py +0 -0
  132. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/single_agent/env.py +0 -0
  133. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/user_sim/__init__.py +0 -0
  134. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/envs/user_sim/env.py +0 -0
  135. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/episode.py +0 -0
  136. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/errors.py +0 -0
  137. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/__init__.py +0 -0
  138. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/adapter.py +0 -0
  139. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/config.py +0 -0
  140. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/dataset.py +0 -0
  141. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/reflection.py +0 -0
  142. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/gepa/runner.py +0 -0
  143. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/grading.py +0 -0
  144. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/graph.py +0 -0
  145. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harness.py +0 -0
  146. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/__init__.py +0 -0
  147. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/bash/__init__.py +0 -0
  148. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/bash/harness.py +0 -0
  149. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/browser_use/__init__.py +0 -0
  150. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/browser_use/harness.py +0 -0
  151. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/browser_use/program.py +0 -0
  152. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/claude_code/__init__.py +0 -0
  153. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/claude_code/harness.py +0 -0
  154. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/codex/__init__.py +0 -0
  155. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/codex/gate.mjs +0 -0
  156. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/codex/harness.py +0 -0
  157. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/deepseek/__init__.py +0 -0
  158. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/deepseek/gate.mjs +0 -0
  159. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/deepseek/harness.py +0 -0
  160. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/deepseek/usage.mjs +0 -0
  161. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/hermes_agent/__init__.py +0 -0
  162. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/hermes_agent/harness.py +0 -0
  163. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/hermes_agent/program.py +0 -0
  164. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/kimi_code/__init__.py +0 -0
  165. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/kimi_code/harness.py +0 -0
  166. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/mini_swe_agent/__init__.py +0 -0
  167. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/mini_swe_agent/harness.py +0 -0
  168. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/mini_swe_agent/program.py +0 -0
  169. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/node.py +0 -0
  170. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/null/__init__.py +0 -0
  171. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/null/harness.py +0 -0
  172. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/openclaw/__init__.py +0 -0
  173. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/openclaw/harness.py +0 -0
  174. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/opencode/__init__.py +0 -0
  175. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/opencode/harness.py +0 -0
  176. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/opencode/mcp-ready.js +0 -0
  177. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/pi/__init__.py +0 -0
  178. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/pi/gate.mjs +0 -0
  179. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/pi/harness.py +0 -0
  180. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/prime_agent/__init__.py +0 -0
  181. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/prime_agent/harness.py +0 -0
  182. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/rlm/__init__.py +0 -0
  183. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/rlm/harness.py +0 -0
  184. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/stirrup/__init__.py +0 -0
  185. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/stirrup/harness.py +0 -0
  186. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/stirrup/program.py +0 -0
  187. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/terminus_2/__init__.py +0 -0
  188. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/terminus_2/harness.py +0 -0
  189. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/terminus_2/program.py +0 -0
  190. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/__init__.py +0 -0
  191. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/compaction.py +0 -0
  192. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/core.py +0 -0
  193. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/install.py +0 -0
  194. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/launch.py +0 -0
  195. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/mcp.py +0 -0
  196. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/harnesses/utils/replay.py +0 -0
  197. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/__init__.py +0 -0
  198. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/base.py +0 -0
  199. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/pool.py +0 -0
  200. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/server.py +0 -0
  201. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/tunnel/__init__.py +0 -0
  202. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/tunnel/base.py +0 -0
  203. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/tunnel/custom.py +0 -0
  204. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/interception/tunnel/prime.py +0 -0
  205. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judge.py +0 -0
  206. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/__init__.py +0 -0
  207. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/decision.py +0 -0
  208. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/reference.py +0 -0
  209. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/reference.txt +0 -0
  210. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/rubric.py +0 -0
  211. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/judges/rubric.txt +0 -0
  212. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/mcp/__init__.py +0 -0
  213. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/mcp/launch.py +0 -0
  214. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/mcp/server.py +0 -0
  215. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/mcp/toolset.py +0 -0
  216. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/rollout.py +0 -0
  217. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/__init__.py +0 -0
  218. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/backends/custom.py +0 -0
  219. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/backends/docker.py +0 -0
  220. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/backends/prime.py +0 -0
  221. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/backends/subprocess.py +0 -0
  222. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/base.py +0 -0
  223. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/compose.py +0 -0
  224. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/deployment.py +0 -0
  225. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/egress.py +0 -0
  226. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/limiters.py +0 -0
  227. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/native_compose.py +0 -0
  228. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/private_network.py +0 -0
  229. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/runtime.py +0 -0
  230. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/runtimes/strategies.py +0 -0
  231. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/semantic.py +0 -0
  232. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/session.py +0 -0
  233. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/state.py +0 -0
  234. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/task.py +0 -0
  235. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/taskset.py +0 -0
  236. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/__init__.py +0 -0
  237. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/harbor/__init__.py +0 -0
  238. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/harbor/toolset.py +0 -0
  239. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/nemo_gym/__init__.py +0 -0
  240. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/nemo_gym/response.py +0 -0
  241. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/nemo_gym/server.py +0 -0
  242. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/nemo_gym/taskset.py +0 -0
  243. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/nemo_gym/toolset.py +0 -0
  244. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/openenv/__init__.py +0 -0
  245. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/openenv/taskset.py +0 -0
  246. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/textarena/__init__.py +0 -0
  247. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/tasksets/textarena/taskset.py +0 -0
  248. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/trace.py +0 -0
  249. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/types.py +0 -0
  250. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/__init__.py +0 -0
  251. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/aio.py +0 -0
  252. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/compile.py +0 -0
  253. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/decorators.py +0 -0
  254. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/env.py +0 -0
  255. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/eval.py +0 -0
  256. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/format.py +0 -0
  257. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/generic.py +0 -0
  258. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/git.py +0 -0
  259. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/image.py +0 -0
  260. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/interrupt.py +0 -0
  261. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/loaders.py +0 -0
  262. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/logging.py +0 -0
  263. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/memory.py +0 -0
  264. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/network.py +0 -0
  265. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/paths.py +0 -0
  266. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/prime.py +0 -0
  267. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/retries.py +0 -0
  268. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/scope.py +0 -0
  269. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/score.py +0 -0
  270. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/trace_store.py +0 -0
  271. {verifiers-0.4.2.dev27 → verifiers-0.4.2.dev29}/verifiers/v1/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: verifiers
3
- Version: 0.4.2.dev27
3
+ Version: 0.4.2.dev29
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
@@ -102,6 +102,16 @@ def test_exclude_drops_tasks_that_any_list_names() -> None:
102
102
  assert idxs(finite().exclude(idx=":7", names=["t8"])) == [7, 9]
103
103
 
104
104
 
105
+ def test_match_lists_extend_from_files(tmp_path) -> None:
106
+ (tmp_path / "idx.txt").write_text("0:2\n\n5\n")
107
+ (tmp_path / "names.txt").write_text(" t8 \nt9\n")
108
+ files = {"idx": tmp_path / "idx.txt", "names": tmp_path / "names.txt"}
109
+ assert idxs(finite().include(idx=[3], files=files)) == [0, 1, 3, 5, 8, 9]
110
+ config = vf.SelectConfig(exclude={"files": {"names": tmp_path / "names.txt"}})
111
+ assert config.exclude.names == ["t8", "t9"] and config.exclude.files.names is None
112
+ assert idxs(finite().select(config)) == list(range(8))
113
+
114
+
105
115
  def test_unmatched_entries_warn(caplog) -> None:
106
116
  with caplog.at_level(logging.WARNING):
107
117
  assert idxs(finite().include(names=["t1", "typo"], idx=[42])) == [1]
@@ -0,0 +1,114 @@
1
+ """Artifact restore: the grading box extracts only what host-side validation read."""
2
+
3
+ import asyncio
4
+ import io
5
+ import shutil
6
+ import subprocess
7
+ import tarfile
8
+ from types import SimpleNamespace
9
+
10
+ import pytest
11
+
12
+ from verifiers.v1.utils.artifacts import restore
13
+
14
+
15
+ class RecordingRuntime:
16
+ """Records the archives `restore` sends and accepts every command."""
17
+
18
+ def __init__(self) -> None:
19
+ self.config = SimpleNamespace(type="docker", mounts={}, workdir="/app")
20
+ self.written: list[bytes] = []
21
+
22
+ async def write(self, path: str, data: bytes) -> None:
23
+ self.written.append(data)
24
+
25
+ async def run(self, argv, env):
26
+ return SimpleNamespace(exit_code=0, stdout="", stderr="")
27
+
28
+
29
+ def make_archive(build) -> bytes:
30
+ buffer = io.BytesIO()
31
+ with tarfile.open(fileobj=buffer, mode="w:", format=tarfile.GNU_FORMAT) as tar:
32
+ build(tar)
33
+ return buffer.getvalue()
34
+
35
+
36
+ def add_file(tar: tarfile.TarFile, name: str, data: bytes, **fields) -> None:
37
+ info = tarfile.TarInfo(name)
38
+ info.size = len(data)
39
+ for key, value in fields.items():
40
+ setattr(info, key, value)
41
+ tar.addfile(info, io.BytesIO(data))
42
+
43
+
44
+ def restored(archive: bytes, root: str = "/app/results") -> bytes:
45
+ runtime = RecordingRuntime()
46
+ asyncio.run(restore(runtime, {root: archive}))
47
+ assert len(runtime.written) == 1
48
+ return runtime.written[0]
49
+
50
+
51
+ def test_restore_keeps_owners_and_drops_special_mode_bits() -> None:
52
+ def build(tar):
53
+ directory = tarfile.TarInfo("app/results")
54
+ directory.type, directory.mode, directory.uid = tarfile.DIRTYPE, 0o1777, 1000
55
+ tar.addfile(directory)
56
+ add_file(
57
+ tar, "app/results/run", b"#!/bin/sh\n", mode=0o4755, uid=1000, gid=1000
58
+ )
59
+
60
+ with tarfile.open(fileobj=io.BytesIO(restored(make_archive(build)))) as tar:
61
+ members = {m.name: m for m in tar.getmembers()}
62
+
63
+ assert members["app/results"].mode == 0o777
64
+ assert members["app/results/run"].mode == 0o755
65
+ assert (members["app/results/run"].uid, members["app/results/run"].gid) == (
66
+ 1000,
67
+ 1000,
68
+ )
69
+
70
+
71
+ @pytest.mark.skipif(shutil.which("tar") is None, reason="needs a system tar")
72
+ def test_system_tar_lists_exactly_what_python_read() -> None:
73
+ long_name = "app/results/" + "nested/" * 20 + "phase_map.csv"
74
+
75
+ def build(tar):
76
+ add_file(tar, "app/results/a.txt", b"a")
77
+ add_file(tar, long_name, b"b")
78
+ link = tarfile.TarInfo("app/results/link")
79
+ link.type, link.linkname = tarfile.SYMTYPE, "a.txt"
80
+ tar.addfile(link)
81
+ hard = tarfile.TarInfo("app/results/hard")
82
+ hard.type, hard.linkname = tarfile.LNKTYPE, "app/results/a.txt"
83
+ tar.addfile(hard)
84
+
85
+ original = make_archive(build)
86
+ with tarfile.open(fileobj=io.BytesIO(original)) as tar:
87
+ python_names = [m.name for m in tar.getmembers()]
88
+
89
+ listing = subprocess.run(
90
+ ["tar", "-tf", "-"],
91
+ input=restored(original),
92
+ capture_output=True,
93
+ check=True,
94
+ ).stdout.decode()
95
+
96
+ assert listing.splitlines() == python_names
97
+
98
+
99
+ def test_restore_refuses_sparse_members() -> None:
100
+ # A few header bytes declare a terabyte: rebuilding it would fill host memory.
101
+ header = tarfile.TarInfo("app/results/hole")
102
+ header.type = tarfile.GNUTYPE_SPARSE
103
+ block = bytearray(header.tobuf(format=tarfile.GNU_FORMAT))
104
+ block[386:398] = b"00000000000\0" # first sparse entry: offset 0
105
+ block[398:410] = b"00000000000\0" # ... holding 0 bytes of data
106
+ block[483:495] = b"%011o\0" % (1 << 40) # real size
107
+ block[148:156] = b" " * 8
108
+ block[148:156] = b"%06o\0 " % sum(block)
109
+ archive = bytes(block) + bytes(2 * tarfile.BLOCKSIZE)
110
+
111
+ runtime = RecordingRuntime()
112
+ with pytest.raises(RuntimeError, match="sparse"):
113
+ asyncio.run(restore(runtime, {"/app/results": archive}))
114
+ assert runtime.written == []
@@ -3,13 +3,24 @@
3
3
 
4
4
  import re
5
5
  import sys
6
+ from pathlib import Path
6
7
 
7
- from pydantic import AliasChoices, Field, field_validator
8
+ from pydantic import AliasChoices, Field, field_validator, model_validator
8
9
  from pydantic_config import BaseConfig
9
10
 
10
11
  IDX_RANGE = re.compile(r"(\d*):(\d*)(?::(\d*))?")
11
12
 
12
13
 
14
+ class TaskMatchFilesConfig(BaseConfig):
15
+ """Newline-delimited text files with more entries for each `TaskMatchConfig`
16
+ list, one entry per line. Blank lines are skipped."""
17
+
18
+ idx: Path | None = None
19
+ ids: Path | None = None
20
+ keys: Path | None = None
21
+ names: Path | None = None
22
+
23
+
13
24
  class TaskMatchConfig(BaseConfig):
14
25
  """Tasks named by position or identity; a task matches when any list names it."""
15
26
 
@@ -24,6 +35,10 @@ class TaskMatchConfig(BaseConfig):
24
35
  """`Task.key` values, as recorded on each trace (`task.key`)."""
25
36
  names: list[str] = Field(default_factory=list)
26
37
  """`TaskData.name` values."""
38
+ files: TaskMatchFilesConfig = TaskMatchFilesConfig()
39
+ """Files whose lines extend the lists above, for selections too long to write
40
+ inline (`files.ids = "ids.txt"`). Validation reads them into the lists and
41
+ resets `files`."""
27
42
 
28
43
  @field_validator("idx", mode="before")
29
44
  @classmethod
@@ -36,6 +51,19 @@ class TaskMatchConfig(BaseConfig):
36
51
  items.extend(_parse_idx_item(part) for part in parts)
37
52
  return items
38
53
 
54
+ @model_validator(mode="after")
55
+ def _read_files(self):
56
+ for field, path in self.files:
57
+ if path is None:
58
+ continue
59
+ lines = [line.strip() for line in path.read_text().splitlines()]
60
+ entries = [line for line in lines if line]
61
+ if field == "idx":
62
+ entries = [_parse_idx_item(entry) for entry in entries]
63
+ getattr(self, field).extend(entries)
64
+ self.files = TaskMatchFilesConfig()
65
+ return self
66
+
39
67
  @property
40
68
  def empty(self) -> bool:
41
69
  return not (self.idx or self.ids or self.keys or self.names)
@@ -410,7 +410,10 @@ class HarborTask(Task[HarborData, State, HarborTaskConfig]):
410
410
  "mkdir -p /tests && tar -xzf /tmp/tests.tgz -C /tests"
411
411
  )
412
412
  # Verifier subprocesses can use different users; opening their log directory
413
- # requires its owner or root, even when it is already writable.
413
+ # requires its owner or root, even when it is already writable. A grading box
414
+ # of its own starts with an empty log directory: anything already there came
415
+ # from the image or from the agent's restored artifacts, not from this grader.
416
+ isolated = wipe or self.scoring.mode == "isolated"
414
417
  setup = (
415
418
  runtime.with_user("root")
416
419
  if runtime.backend.SUPPORTS_USER_SWITCHING
@@ -420,6 +423,7 @@ class HarborTask(Task[HarborData, State, HarborTaskConfig]):
420
423
  (
421
424
  setup,
422
425
  (
426
+ f"{'rm -rf /logs/verifier && ' if isolated else ''}"
423
427
  f"mkdir -p /logs/verifier {ARTIFACTS_DIR} && "
424
428
  f"chmod 777 /logs/verifier {ARTIFACTS_DIR}"
425
429
  ),
@@ -341,6 +341,10 @@ async def restore(runtime: Runtime, collected: dict[str, bytes | None]) -> None:
341
341
  )
342
342
  for root, archive in collected.items():
343
343
  _validate_restore(root, archive)
344
+ collected = {
345
+ root: None if archive is None else _rebuild(archive)
346
+ for root, archive in collected.items()
347
+ }
344
348
  # Clear every root up front, not per entry: a later nested root would otherwise
345
349
  # delete content an earlier one just restored. Clearing also drops any file or
346
350
  # symlink the image left at the target.
@@ -425,6 +429,45 @@ def _drop_special_files(archive: bytes) -> tuple[bytes, list[str]]:
425
429
  return out.getvalue(), dropped
426
430
 
427
431
 
432
+ def _rebuild(archive: bytes) -> bytes:
433
+ """Write a fresh archive holding exactly the members Python's tarfile reads.
434
+
435
+ Validation reads archives with Python's tarfile, but the grading box extracts
436
+ them with its own tar. An agent controls the archive bytes, and two tar parsers
437
+ can disagree about the same bytes, so the box only ever extracts an archive
438
+ written here from the members validation saw. Owners and permission bits are
439
+ kept (tasks rely on ownership, e.g. git's safe.directory check); setuid,
440
+ setgid and sticky bits are not.
441
+ """
442
+ out = io.BytesIO()
443
+ with (
444
+ tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as source,
445
+ tarfile.open(fileobj=out, mode="w:", format=tarfile.PAX_FORMAT) as target,
446
+ ):
447
+ for member in source:
448
+ info = tarfile.TarInfo(member.name)
449
+ info.mode = member.mode & 0o777
450
+ info.uid, info.gid = member.uid, member.gid
451
+ info.uname, info.gname = member.uname, member.gname
452
+ info.mtime = member.mtime
453
+ content = None
454
+ if member.isfile():
455
+ info.type = tarfile.REGTYPE
456
+ info.size = member.size
457
+ content = source.extractfile(member)
458
+ elif member.isdir():
459
+ info.type = tarfile.DIRTYPE
460
+ elif member.issym() or member.islnk():
461
+ info.type = tarfile.SYMTYPE if member.issym() else tarfile.LNKTYPE
462
+ info.linkname = member.linkname
463
+ else:
464
+ raise RuntimeError(
465
+ f"artifact member {member.name!r} is a link or special file"
466
+ )
467
+ target.addfile(info, content)
468
+ return out.getvalue()
469
+
470
+
428
471
  def _validate_restore(root: str, archive: bytes | None) -> None:
429
472
  """Reject archive content that could restore outside its declared root."""
430
473
  root_path = PurePosixPath(root)
@@ -482,6 +525,15 @@ def _validate_restore(root: str, archive: bytes | None) -> None:
482
525
  raise RuntimeError(
483
526
  f"artifact member {member.name!r} is a link or special file"
484
527
  )
528
+ if member.issparse():
529
+ # A sparse member declares its full size in a few header bytes,
530
+ # and rebuilding the archive on the host would write every hole
531
+ # out as zeros. GNU tar only writes these with --sparse; bsdtar
532
+ # writes them for any file with holes.
533
+ raise RuntimeError(
534
+ f"artifact member {member.name!r} is a sparse file, which "
535
+ "cannot be restored"
536
+ )
485
537
  if member.isfile():
486
538
  regular_files.add(member_path)
487
539
  # Check after reading the whole archive, so a later symlink cannot
File without changes