benchflow 0.5.3.dev956__tar.gz → 0.5.3.dev976__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 (425) hide show
  1. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/PKG-INFO +1 -1
  2. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/pyproject.toml +1 -1
  3. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/task_authoring.py +33 -1
  4. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/cli/main.py +40 -0
  5. benchflow-0.5.3.dev976/src/benchflow/sandbox/_cache_reclaim.py +97 -0
  6. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/lockdown.py +8 -14
  7. benchflow-0.5.3.dev976/tests/fixtures/digest/README.md +9 -0
  8. benchflow-0.5.3.dev976/tests/fixtures/digest/alpha-task/environment/Dockerfile +1 -0
  9. benchflow-0.5.3.dev976/tests/fixtures/digest/alpha-task/instruction.md +3 -0
  10. benchflow-0.5.3.dev976/tests/fixtures/digest/alpha-task/task.toml +4 -0
  11. benchflow-0.5.3.dev976/tests/fixtures/digest/alpha-task/tests/test.sh +2 -0
  12. benchflow-0.5.3.dev976/tests/fixtures/digest/beta-task/data/unicode.txt +1 -0
  13. benchflow-0.5.3.dev976/tests/fixtures/digest/beta-task/instruction.md +1 -0
  14. benchflow-0.5.3.dev976/tests/fixtures/digest/beta-task/task.toml +1 -0
  15. benchflow-0.5.3.dev976/tests/fixtures/digest/not-a-task/notes.txt +1 -0
  16. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_hardening.py +106 -50
  17. benchflow-0.5.3.dev976/tests/test_task_digest.py +161 -0
  18. benchflow-0.5.3.dev976/tests/trajectories/__init__.py +0 -0
  19. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/.gitignore +0 -0
  20. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/CHANGELOG.md +0 -0
  21. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/LICENSE +0 -0
  22. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/README.md +0 -0
  23. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/__init__.py +0 -0
  24. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_dotenv.py +0 -0
  25. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_paths.py +0 -0
  26. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_run.py +0 -0
  27. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_types.py +0 -0
  28. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/__init__.py +0 -0
  29. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/benchmark_repos.py +0 -0
  30. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/config.py +0 -0
  31. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/evaluation_results.py +0 -0
  32. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/json_safe.py +0 -0
  33. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/learner_memory.py +0 -0
  34. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/result_metadata.py +0 -0
  35. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/reward_events.py +0 -0
  36. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/scoring.py +0 -0
  37. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/source_provenance.py +0 -0
  38. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/_utils/yaml_loader.py +0 -0
  39. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/__init__.py +0 -0
  40. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/client.py +0 -0
  41. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/container_transport.py +0 -0
  42. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/runtime.py +0 -0
  43. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/session.py +0 -0
  44. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/transport.py +0 -0
  45. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/acp/types.py +0 -0
  46. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/__init__.py +0 -0
  47. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/harbor.py +0 -0
  48. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/inbound.py +0 -0
  49. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/inspect_ai.py +0 -0
  50. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/ors.py +0 -0
  51. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/adapters/terminal_bench.py +0 -0
  52. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/__init__.py +0 -0
  53. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/codex_config.py +0 -0
  54. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/credentials.py +0 -0
  55. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/env.py +0 -0
  56. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/errors.py +0 -0
  57. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/harvey_lab_acp_shim.py +0 -0
  58. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/install.py +0 -0
  59. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/openclaw_acp_shim.py +0 -0
  60. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/pi_acp_launcher.py +0 -0
  61. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/protocol.py +0 -0
  62. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/providers.py +0 -0
  63. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/agents/registry.py +0 -0
  64. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/branch.py +0 -0
  65. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/cli/__init__.py +0 -0
  66. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/cli/continue_cmd.py +0 -0
  67. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/cli/trace_import.py +0 -0
  68. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/compat/__init__.py +0 -0
  69. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/compat/harbor_registry.py +0 -0
  70. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/__init__.py +0 -0
  71. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/batch.py +0 -0
  72. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/orchestrator.py +0 -0
  73. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/replay_proxy.py +0 -0
  74. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/run_folder.py +0 -0
  75. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/continue_run/sandbox_proxy.py +0 -0
  76. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/contracts/__init__.py +0 -0
  77. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/contracts/planes.py +0 -0
  78. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/contracts/user.py +0 -0
  79. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/demo_task/environment/Dockerfile +0 -0
  80. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/demo_task/instruction.md +0 -0
  81. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/demo_task/task.toml +0 -0
  82. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/demo_task/tests/test.sh +0 -0
  83. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/diagnostics.py +0 -0
  84. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/environment/__init__.py +0 -0
  85. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/environment/manifest.py +0 -0
  86. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/environment/manifest_env.py +0 -0
  87. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/environment/protocol.py +0 -0
  88. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/environment/readiness.py +0 -0
  89. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/eval_sharding.py +0 -0
  90. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/eval_worker.py +0 -0
  91. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/evaluation.py +0 -0
  92. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/experimental/__init__.py +0 -0
  93. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/experimental/mcp/__init__.py +0 -0
  94. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/experimental/mcp/hooks.py +0 -0
  95. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/experimental/mcp/reviewer_server.py +0 -0
  96. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/hosted_env.py +0 -0
  97. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/learner_skills.py +0 -0
  98. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/learner_store.py +0 -0
  99. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/metrics.py +0 -0
  100. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/models.py +0 -0
  101. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/monitor.py +0 -0
  102. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/__init__.py +0 -0
  103. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/litellm_bedrock_patch.py +0 -0
  104. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/litellm_bedrock_preflight.py +0 -0
  105. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/litellm_config.py +0 -0
  106. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/litellm_logging.py +0 -0
  107. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/litellm_runtime.py +0 -0
  108. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/providers/runtime.py +0 -0
  109. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/py.typed +0 -0
  110. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/README.md +0 -0
  111. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/__init__.py +0 -0
  112. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/builtins.py +0 -0
  113. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/events.py +0 -0
  114. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/file_readers.py +0 -0
  115. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/llm.py +0 -0
  116. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/memory_scorer.py +0 -0
  117. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/node.py +0 -0
  118. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/protocol.py +0 -0
  119. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/rubric.py +0 -0
  120. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/rubric_config.py +0 -0
  121. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rewards/validation.py +0 -0
  122. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rollout.py +0 -0
  123. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rollout_branch.py +0 -0
  124. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/rollout_planes.py +0 -0
  125. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/runtime.py +0 -0
  126. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/__init__.py +0 -0
  127. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_base.py +0 -0
  128. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_compose.py +0 -0
  129. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_compose_files/docker-compose-base.yaml +0 -0
  130. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_compose_files/docker-compose-build.yaml +0 -0
  131. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_compose_files/docker-compose-no-network.yaml +0 -0
  132. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_compose_files/docker-compose-prebuilt.yaml +0 -0
  133. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/_sdk_ops.py +0 -0
  134. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/daytona.py +0 -0
  135. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/docker.py +0 -0
  136. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/metadata.py +0 -0
  137. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/modal_impl.py +0 -0
  138. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/process.py +0 -0
  139. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/protocol.py +0 -0
  140. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/services.py +0 -0
  141. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/setup.py +0 -0
  142. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/snapshot.py +0 -0
  143. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sandbox/user.py +0 -0
  144. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/scenes.py +0 -0
  145. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/sdk.py +0 -0
  146. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/self_gen.py +0 -0
  147. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skill_eval/__init__.py +0 -0
  148. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skill_eval/_core.py +0 -0
  149. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skill_eval/gepa_export.py +0 -0
  150. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skill_eval/schema.py +0 -0
  151. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skill_policy.py +0 -0
  152. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/skills.py +0 -0
  153. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/__init__.py +0 -0
  154. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/config.py +0 -0
  155. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/env.py +0 -0
  156. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/paths.py +0 -0
  157. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/task.py +0 -0
  158. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/task/verifier.py +0 -0
  159. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/templates/__init__.py +0 -0
  160. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/templates/judge.py.tmpl +0 -0
  161. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/templates/test.sh.tmpl +0 -0
  162. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/__init__.py +0 -0
  163. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/huggingface.py +0 -0
  164. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/local.py +0 -0
  165. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/models.py +0 -0
  166. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/parsers.py +0 -0
  167. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/traces/task_gen.py +0 -0
  168. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/__init__.py +0 -0
  169. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/_capture.py +0 -0
  170. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/export.py +0 -0
  171. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/metrics.py +0 -0
  172. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/otel.py +0 -0
  173. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/tree.py +0 -0
  174. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/types.py +0 -0
  175. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/trajectories/viewer.py +0 -0
  176. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/src/benchflow/usage_tracking.py +0 -0
  177. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/__init__.py +0 -0
  178. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/agents/__init__.py +0 -0
  179. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/agents/test_protocol.py +0 -0
  180. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/README.md +0 -0
  181. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/environment/Dockerfile +0 -0
  182. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/environment/docker-compose.yaml +0 -0
  183. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/environment/skills/conformance-writer/SKILL.md +0 -0
  184. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/instruction.md +0 -0
  185. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/solution/solve.sh +0 -0
  186. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/task.toml +0 -0
  187. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/acp_smoke/tests/test.sh +0 -0
  188. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/conformance-results.json +0 -0
  189. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/proof_multi_agent.py +0 -0
  190. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/proof_snapshot.py +0 -0
  191. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/run_conformance.py +0 -0
  192. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conformance/self_gen_smoke_skills/skill-creator/SKILL.md +0 -0
  193. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/conftest.py +0 -0
  194. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/__init__.py +0 -0
  195. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/_helpers.py +0 -0
  196. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/test_batch.py +0 -0
  197. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/test_orchestrator.py +0 -0
  198. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/test_replay_proxy.py +0 -0
  199. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/continue_run/test_run_folder.py +0 -0
  200. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/__init__.py +0 -0
  201. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_chibench_manifest.py +0 -0
  202. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_clawsbench_manifest.py +0 -0
  203. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_manifest.py +0 -0
  204. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_manifest_env.py +0 -0
  205. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_protocol.py +0 -0
  206. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/environment/test_readiness.py +0 -0
  207. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/hello-world-task/environment/Dockerfile +0 -0
  208. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/hello-world-task/instruction.md +0 -0
  209. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/hello-world-task/solution/solve.sh +0 -0
  210. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/hello-world-task/task.toml +0 -0
  211. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/hello-world-task/tests/test.sh +0 -0
  212. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/environment/Dockerfile +0 -0
  213. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/instruction.md +0 -0
  214. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/solution/solve.sh +0 -0
  215. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/task.toml +0 -0
  216. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/tests/test.sh +0 -0
  217. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/terminal-bench-smoke-task/tests/test_state.py +0 -0
  218. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/test_claude.sh +0 -0
  219. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/test_codex.sh +0 -0
  220. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/test_codex_custom_provider.sh +0 -0
  221. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/test_gemini.sh +0 -0
  222. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/test_openclaw.sh +0 -0
  223. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/traces/minimal-claude.jsonl +0 -0
  224. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/examples/traces/minimal-opentraces.jsonl +0 -0
  225. /benchflow-0.5.3.dev956/tests/trajectories/__init__.py → /benchflow-0.5.3.dev976/tests/fixtures/digest/beta-task/data/empty.bin +0 -0
  226. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/fixtures/mock_acp_agent.py +0 -0
  227. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/fixtures/mock_acp_agent_interleaved.py +0 -0
  228. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/fixtures/mock_acp_agent_multi_turn.py +0 -0
  229. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/fixtures/mock_openai_responses_server.py +0 -0
  230. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/check_adapter_evidence.py +0 -0
  231. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/check_hosted_env_evidence.py +0 -0
  232. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/check_results.py +0 -0
  233. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/check_skillsbench_harbor_parity.py +0 -0
  234. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/check_trace_to_task_evidence.py +0 -0
  235. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/claude-agent-acp.yaml +0 -0
  236. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/codex-acp.yaml +0 -0
  237. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/gemini.yaml +0 -0
  238. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/harvey-lab-harness.yaml +0 -0
  239. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/openclaw.yaml +0 -0
  240. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/opencode.yaml +0 -0
  241. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/openhands.yaml +0 -0
  242. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/configs/pi-acp.yaml +0 -0
  243. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/run.sh +0 -0
  244. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/run_suite.py +0 -0
  245. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/integration/suites/release.yaml +0 -0
  246. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_acp.py +0 -0
  247. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_acp_capability_advertising.py +0 -0
  248. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_acp_model_config_dispatch.py +0 -0
  249. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_acp_pinned_protocol_guard.py +0 -0
  250. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_acp_setup_failure_propagation.py +0 -0
  251. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_adapter_scripts.py +0 -0
  252. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_adapters.py +0 -0
  253. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_cli.py +0 -0
  254. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_env_resolution.py +0 -0
  255. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_gemini_defaults.py +0 -0
  256. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_idle_timeout_cli.py +0 -0
  257. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_model_decouple.py +0 -0
  258. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_registry.py +0 -0
  259. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_setup.py +0 -0
  260. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_agent_spec.py +0 -0
  261. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_base_install_imports.py +0 -0
  262. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_bedrock_thinking.py +0 -0
  263. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_branch.py +0 -0
  264. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_capture_trajectory.py +0 -0
  265. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_clawsbench_slice.py +0 -0
  266. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_cli_daytona.py +0 -0
  267. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_cli_docs_drift.py +0 -0
  268. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_cli_misc.py +0 -0
  269. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_compat_harbor_registry.py +0 -0
  270. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_config_redaction.py +0 -0
  271. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_connect_as_env.py +0 -0
  272. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_continuallearningbench_adapter.py +0 -0
  273. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_credential_env_scrub.py +0 -0
  274. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_daytona_key.py +0 -0
  275. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_no_host_paths.py +0 -0
  276. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_release_evidence.py +0 -0
  277. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_roadmap.py +0 -0
  278. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_symlink_ingestion.py +0 -0
  279. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_dashboard_sync.py +0 -0
  280. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_daytona_command_polling.py +0 -0
  281. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_daytona_litellm_runtime.py +0 -0
  282. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_daytona_status.py +0 -0
  283. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_docker_prune_scoping.py +0 -0
  284. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_docker_uploads.py +0 -0
  285. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_docs_examples.py +0 -0
  286. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eng50_capabilities.py +0 -0
  287. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_env_setup.py +0 -0
  288. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_environment_manifest_controls.py +0 -0
  289. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_filters_applied.py +0 -0
  290. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_sharding.py +0 -0
  291. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_single_task_summary.py +0 -0
  292. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_source_provenance.py +0 -0
  293. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_worker_retry.py +0 -0
  294. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_eval_zero_task_guard.py +0 -0
  295. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_evaluation_environment_manifest.py +0 -0
  296. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_exclude_tasks.py +0 -0
  297. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_experiments_status.py +0 -0
  298. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_harvey_lab_shim.py +0 -0
  299. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_hf_scores.py +0 -0
  300. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_hilbench_adapter.py +0 -0
  301. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_hosted_env.py +0 -0
  302. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_hosted_env_rollout_contract.py +0 -0
  303. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_inbound_adapter_manifest.py +0 -0
  304. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_inbound_adapters.py +0 -0
  305. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_integration_check_results.py +0 -0
  306. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_integration_run_suite.py +0 -0
  307. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_internet_policy.py +0 -0
  308. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_job.py +0 -0
  309. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_job_sequential_shared.py +0 -0
  310. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_job_sequential_shared_resume.py +0 -0
  311. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_judge_symlink_ingestion.py +0 -0
  312. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_learner_skills.py +0 -0
  313. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_learner_skills_traversal.py +0 -0
  314. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_learner_store.py +0 -0
  315. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_learner_store_persistence.py +0 -0
  316. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_litellm_config.py +0 -0
  317. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_litellm_hardening.py +0 -0
  318. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_litellm_logging.py +0 -0
  319. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_litellm_runtime.py +0 -0
  320. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_litellm_smoke.py +0 -0
  321. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_llm_judge.py +0 -0
  322. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_llm_judge_event_tags.py +0 -0
  323. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_llm_judge_verifier.py +0 -0
  324. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_memory_scorer.py +0 -0
  325. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_metrics.py +0 -0
  326. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_mock_openai_responses_server.py +0 -0
  327. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_monitor_scaffold.py +0 -0
  328. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_native_acp_usage.py +0 -0
  329. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_no_cross_provider_fallback.py +0 -0
  330. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_notification_order_real.py +0 -0
  331. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_oracle.py +0 -0
  332. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_oracle_chokepoint.py +0 -0
  333. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_paths_safe.py +0 -0
  334. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_paths_symlink_helpers.py +0 -0
  335. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_pi_acp_launcher.py +0 -0
  336. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_process.py +0 -0
  337. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_provider_auth_detection.py +0 -0
  338. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_providers.py +0 -0
  339. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_reexport.py +0 -0
  340. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_registry_invariants.py +0 -0
  341. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_release_version.py +0 -0
  342. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_resolve_env_helpers.py +0 -0
  343. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_reward_node.py +0 -0
  344. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_reward_unified_contract.py +0 -0
  345. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rewards.py +0 -0
  346. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rewards_jsonl.py +0 -0
  347. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_architecture.py +0 -0
  348. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_branch.py +0 -0
  349. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_config_path_coercion.py +0 -0
  350. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_environment.py +0 -0
  351. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_import_no_side_effects.py +0 -0
  352. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_on_ask_user_wiring.py +0 -0
  353. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_probe_sandbox_health.py +0 -0
  354. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rollout_upload.py +0 -0
  355. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_rubric_config.py +0 -0
  356. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_runtime.py +0 -0
  357. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_runtime_config_wired.py +0 -0
  358. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_runtime_live_sandbox.py +0 -0
  359. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox.py +0 -0
  360. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_exec_secret_handling.py +0 -0
  361. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_isolation_copy_traversal.py +0 -0
  362. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_multi_service.py +0 -0
  363. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_protocol.py +0 -0
  364. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_setup.py +0 -0
  365. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_snapshot_contract.py +0 -0
  366. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_upload_symlink.py +0 -0
  367. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sandbox_verifier_workspace.py +0 -0
  368. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_scene.py +0 -0
  369. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_scene_outbox_trial.py +0 -0
  370. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_scene_parallel_group.py +0 -0
  371. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_scene_result_aggregation.py +0 -0
  372. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_scoring.py +0 -0
  373. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sdk_internals.py +0 -0
  374. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_sdk_lockdown.py +0 -0
  375. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_self_gen_cli.py +0 -0
  376. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_self_gen_export_error_channel.py +0 -0
  377. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_self_gen_export_failures.py +0 -0
  378. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_self_gen_orchestration.py +0 -0
  379. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_session_request_permission_dispatch.py +0 -0
  380. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_eval.py +0 -0
  381. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_eval_dryrun.py +0 -0
  382. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_eval_integration.py +0 -0
  383. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_eval_sweep.py +0 -0
  384. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_eval_traversal.py +0 -0
  385. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_invocation_artifacts.py +0 -0
  386. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skill_policy.py +0 -0
  387. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skills.py +0 -0
  388. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skills_dir_agent_home_link.py +0 -0
  389. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skillsbench_harbor_parity.py +0 -0
  390. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skillsbench_harbor_run_suite.py +0 -0
  391. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_skillsbench_publish_scrub.py +0 -0
  392. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_smoke.py +0 -0
  393. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_subscription_auth.py +0 -0
  394. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_task_check_eval_consistency.py +0 -0
  395. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_task_config.py +0 -0
  396. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_task_download.py +0 -0
  397. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_tasks.py +0 -0
  398. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_token_usage_normalization.py +0 -0
  399. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trace_import_cli.py +0 -0
  400. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trace_task_gen_traversal.py +0 -0
  401. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trace_to_task_evidence.py +0 -0
  402. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_traces_huggingface.py +0 -0
  403. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_traces_parsers.py +0 -0
  404. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_traces_task_gen.py +0 -0
  405. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_train_mode_artifact_emission.py +0 -0
  406. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trajectory_integration.py +0 -0
  407. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trajectory_streaming.py +0 -0
  408. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trial_agent_timeout_verify.py +0 -0
  409. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trial_install_agent_timeout.py +0 -0
  410. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_trial_litellm_runtime.py +0 -0
  411. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_usage_litellm.py +0 -0
  412. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_usage_required.py +0 -0
  413. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_usage_tracking.py +0 -0
  414. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_user.py +0 -0
  415. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_verifier_multi_container.py +0 -0
  416. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_verifier_output.py +0 -0
  417. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_verifier_output_freshness.py +0 -0
  418. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_verify.py +0 -0
  419. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_workflow_action_pinning.py +0 -0
  420. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/test_yaml_config.py +0 -0
  421. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/trajectories/test_export.py +0 -0
  422. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/trajectories/test_export_nan_handling.py +0 -0
  423. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/trajectories/test_redaction.py +0 -0
  424. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/trajectories/test_step_granularity.py +0 -0
  425. {benchflow-0.5.3.dev956 → benchflow-0.5.3.dev976}/tests/trajectories/test_tree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: benchflow
3
- Version: 0.5.3.dev956
3
+ Version: 0.5.3.dev976
4
4
  Summary: Multi-turn agent benchmarking with ACP — run any agent, any model, any provider.
5
5
  Project-URL: Homepage, https://github.com/benchflow-ai/benchflow
6
6
  Project-URL: Repository, https://github.com/benchflow-ai/benchflow
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "benchflow"
3
- version = "0.5.3.dev956"
3
+ version = "0.5.3.dev976"
4
4
  description = "Multi-turn agent benchmarking with ACP — run any agent, any model, any provider."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,8 @@
1
- """Task authoring — init and check benchmark tasks."""
1
+ """Task authoring — init, check, and digest benchmark tasks."""
2
2
 
3
+ import hashlib
3
4
  import logging
5
+ import os
4
6
  import re
5
7
  import tomllib
6
8
  from pathlib import Path
@@ -94,6 +96,36 @@ def check_task(task_dir: Path) -> list[str]:
94
96
  return issues
95
97
 
96
98
 
99
+ def task_digest(task_dir: Path) -> str:
100
+ """Content digest pinning a task's files, independent of git.
101
+
102
+ sha256 over every regular file under ``task_dir``, sorted by POSIX
103
+ relative path; each file contributes
104
+ ``path_utf8 + b"\\x00" + sha256(file_bytes).digest()``. Symlinks and
105
+ file modes are excluded, so the digest is reproducible from a plain
106
+ checkout. Must byte-match the reference digests in the skillsbench
107
+ dataset registry (``registry.json`` / ``docs/dataset-versioning.md``,
108
+ skillsbench PR #922).
109
+ """
110
+ if not task_dir.is_dir():
111
+ raise NotADirectoryError(f"Not a directory: {task_dir}")
112
+ files: list[tuple[str, Path]] = []
113
+ # os.walk never descends into symlinked directories (unlike pre-3.13
114
+ # Path.rglob), keeping "symlinks are excluded" true for whole subtrees.
115
+ for dirpath, _dirnames, filenames in os.walk(task_dir):
116
+ for filename in filenames:
117
+ path = Path(dirpath) / filename
118
+ if path.is_symlink() or not path.is_file():
119
+ continue
120
+ files.append((path.relative_to(task_dir).as_posix(), path))
121
+ digest = hashlib.sha256()
122
+ for rel, path in sorted(files):
123
+ digest.update(rel.encode("utf-8"))
124
+ digest.update(b"\x00")
125
+ digest.update(hashlib.sha256(path.read_bytes()).digest())
126
+ return f"sha256:{digest.hexdigest()}"
127
+
128
+
97
129
  _CTRF_STANDARD_PATH = "/logs/verifier/ctrf.json"
98
130
 
99
131
 
@@ -674,6 +674,46 @@ def tasks_check(
674
674
  raise typer.Exit(1)
675
675
 
676
676
 
677
+ @tasks_app.command("digest")
678
+ def tasks_digest(
679
+ path: Annotated[
680
+ Path,
681
+ typer.Argument(
682
+ help="Path to a task directory, or a directory of task directories"
683
+ ),
684
+ ],
685
+ ) -> None:
686
+ """Compute the content digest that pins a task's files, independent of git.
687
+
688
+ Matches the digests in the skillsbench dataset registry (registry.json).
689
+ Given a single task directory (contains task.toml), prints the digest;
690
+ given a directory of tasks, prints one "<name> <digest>" line per task.
691
+ """
692
+ from benchflow._utils.task_authoring import task_digest
693
+
694
+ if not path.is_dir():
695
+ console.print(f"[red]Not a directory: {path}[/red]")
696
+ raise typer.Exit(1)
697
+
698
+ if (path / "task.toml").is_file():
699
+ # typer.echo, not console.print: Rich wraps lines at terminal width,
700
+ # which would corrupt piped machine-readable output.
701
+ typer.echo(task_digest(path))
702
+ return
703
+
704
+ task_dirs = sorted(
705
+ d for d in path.iterdir() if d.is_dir() and (d / "task.toml").is_file()
706
+ )
707
+ if not task_dirs:
708
+ console.print(
709
+ f"[red]No tasks under {path} — expected task.toml in it "
710
+ f"or in its immediate subdirectories[/red]"
711
+ )
712
+ raise typer.Exit(1)
713
+ for task_dir in task_dirs:
714
+ typer.echo(f"{task_dir.name} {task_digest(task_dir)}")
715
+
716
+
677
717
  compat_app = typer.Typer(help="Third-party framework compatibility checks.")
678
718
  app.add_typer(compat_app, name="compat")
679
719
 
@@ -0,0 +1,97 @@
1
+ """Pre-verifier cache reclaim command construction.
2
+
3
+ The sandbox cannot import benchflow internals, so the reclaim implementation is
4
+ an embedded Python snippet passed to ``python3 -c``. Keep it here instead of in
5
+ ``lockdown.py`` so the hardening orchestrator stays readable.
6
+ """
7
+
8
+ import shlex
9
+ from textwrap import dedent
10
+
11
+ # Symlink- and workspace-safe cache reclaim for #601.
12
+ #
13
+ # The previous shell form (`rm -rf "$u/.cache/uv" ...; rm -rf /tmp/uv-*`) could
14
+ # delete agent-visible state before scoring: `rm -rf` traverses intermediate
15
+ # symlinks (an agent-planted `~/.cache -> /app` turned the cache delete into
16
+ # `rm -rf /app/uv`), and the `/tmp/uv-*` glob matched a legitimate
17
+ # `/tmp/uv-workspace`. The textual `$WS`/`$u` guard saw none of that. Since
18
+ # ``restore_workspace`` defaults to False, nothing undid the damage.
19
+ #
20
+ # This snippet hardens every deletion candidate:
21
+ # - rejects any candidate with a symlink path component, including the final
22
+ # one, so agent-controlled parents can no longer redirect the delete;
23
+ # - resolves protected roots (workspace + /logs, covering /logs/artifacts and
24
+ # /logs/verifier) via realpath and skips candidates overlapping in either
25
+ # direction, applied uniformly to per-user caches, /tmp globs, and apt;
26
+ # - deletes with shutil.rmtree, which removes child symlinks without following
27
+ # them.
28
+ #
29
+ # argv[1] = active workspace (or /nonexistent). argv[2] = optional filesystem
30
+ # prefix, "" in production; tests pass a temp root so the exact production code
31
+ # runs hermetically against a fake filesystem.
32
+ #
33
+ # Fail-safe by construction: if python3 is unavailable the reclaim simply does
34
+ # not run (the trailing `true` swallows it). Losing best-effort ENOSPC mitigation
35
+ # is strictly better than deleting the wrong state.
36
+ RECLAIM_CACHES_PY = dedent(
37
+ """
38
+ import glob, os, shutil, sys
39
+
40
+ ws = sys.argv[1]
41
+ px = sys.argv[2] if len(sys.argv) > 2 else ''
42
+
43
+ def linked(path):
44
+ p = px or ''
45
+ for part in path[len(px):].strip('/').split('/'):
46
+ p = p + '/' + part
47
+ if os.path.islink(p):
48
+ return True
49
+ return False
50
+
51
+ protected = []
52
+ for root in (ws, px + '/logs'):
53
+ try:
54
+ if root and os.path.exists(root):
55
+ protected.append(os.path.realpath(root))
56
+ except OSError:
57
+ pass
58
+
59
+ def overlaps(path):
60
+ return any(
61
+ path == r or path.startswith(r + '/') or r.startswith(path + '/')
62
+ for r in protected
63
+ )
64
+
65
+ cands = [
66
+ u + '/.cache/' + name
67
+ for u in [px + '/root'] + sorted(glob.glob(px + '/home/*'))
68
+ for name in ('uv', 'pip', 'uv_build')
69
+ ]
70
+ cands += sorted(glob.glob(px + '/tmp/uv-*'))
71
+ cands += sorted(glob.glob(px + '/tmp/.uv-*'))
72
+ cands += sorted(glob.glob(px + '/var/cache/apt/archives/*.deb'))
73
+ for c in cands:
74
+ try:
75
+ if not os.path.lexists(c) or linked(c):
76
+ continue
77
+ # linked() proved no symlink components below the prefix, so
78
+ # realpath only normalizes the prefix itself - consistent with
79
+ # the realpath'd protected roots.
80
+ if overlaps(os.path.realpath(c)):
81
+ continue
82
+ if os.path.isdir(c):
83
+ shutil.rmtree(c, ignore_errors=True)
84
+ else:
85
+ os.remove(c)
86
+ except OSError:
87
+ pass
88
+ """
89
+ ).strip()
90
+
91
+
92
+ def build_reclaim_caches_cmd(workspace: str | None) -> str:
93
+ """Build the best-effort cache reclaim shell command."""
94
+ workspace_arg = shlex.quote(workspace) if workspace else "/nonexistent"
95
+ return (
96
+ f"python3 -c {shlex.quote(RECLAIM_CACHES_PY)} {workspace_arg} 2>/dev/null; true"
97
+ )
@@ -20,6 +20,7 @@ from pathlib import Path
20
20
  from typing import TYPE_CHECKING, Any
21
21
 
22
22
  from benchflow.agents.registry import get_sandbox_home_dirs
23
+ from benchflow.sandbox._cache_reclaim import build_reclaim_caches_cmd
23
24
 
24
25
  if TYPE_CHECKING:
25
26
  from benchflow.task import Task
@@ -877,22 +878,15 @@ async def harden_before_verify(
877
878
  # installed tools, or task assets — and a failure here never blocks the
878
879
  # verifier. Runs on `main` only, consistent with the hardening policy above.
879
880
  #
880
- # Workspace-aware: a task can legitimately use /root or /home/<user> as its
881
- # workspace (so "$u/.cache" would be workspace state the verifier must see
882
- # untouched). Skip any per-user cache that overlaps the active workspace in
883
- # either direction this matters because restore_workspace defaults to False,
884
- # so the later full restore does NOT run to undo a stray deletion.
885
- workspace_guard = shlex.quote(workspace) if workspace else "/nonexistent"
881
+ # Workspace-aware AND symlink-safe (#601): a task can legitimately use
882
+ # /root, /home/<user>, or /tmp/uv-* as its workspace, and an agent can
883
+ # plant `~/.cache -> /app` so a naive `rm -rf "$u/.cache/uv"` traverses
884
+ # into workspace/output state. The reclaim command rejects symlinked
885
+ # candidates and realpath-guards every deletion against the workspace and
886
+ # /logs this matters because restore_workspace defaults to False.
886
887
  try:
887
888
  await env.exec(
888
- f"WS={workspace_guard}; "
889
- "for u in /root /home/*; do "
890
- ' case "$WS" in "$u" | "$u"/*) continue ;; esac; '
891
- ' case "$u" in "$WS"/*) continue ;; esac; '
892
- ' rm -rf "$u/.cache/uv" "$u/.cache/pip" "$u/.cache/uv_build" 2>/dev/null; '
893
- "done; "
894
- "rm -rf /tmp/uv-* /tmp/.uv-* /var/cache/apt/archives/*.deb 2>/dev/null; "
895
- "true",
889
+ build_reclaim_caches_cmd(workspace),
896
890
  user="root",
897
891
  timeout_sec=30,
898
892
  )
@@ -0,0 +1,9 @@
1
+ # Digest test fixtures
2
+
3
+ Golden-test fixtures for `bench tasks digest`. The expected digests are
4
+ hardcoded in `tests/test_task_digest.py` — do not modify any file under the
5
+ task directories here without updating those golden values.
6
+
7
+ Filenames are ASCII on purpose: macOS checks out unicode filenames in NFD,
8
+ which would change the hashed path bytes across platforms. Unicode coverage
9
+ lives in file *content* (`beta-task/data/unicode.txt`).
@@ -0,0 +1,3 @@
1
+ # alpha-task
2
+
3
+ Compute the answer.
@@ -0,0 +1,4 @@
1
+ version = "1.1"
2
+
3
+ [metadata]
4
+ difficulty = "easy"
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ echo "1.0" > /logs/verifier/reward.txt
@@ -0,0 +1 @@
1
+ naïve résumé — ünïcödé
@@ -0,0 +1 @@
1
+ version = "1.1"
@@ -0,0 +1 @@
1
+ Not a task — no task.toml. Multi-task digest mode must skip this directory.
@@ -280,37 +280,36 @@ class TestVerifierDirWipe:
280
280
  async def test_reclaims_redownloadable_caches_before_verify(self):
281
281
  """Frees uv/pip/apt download caches so the verifier's own deps fit on
282
282
  disk-constrained sandboxes (e.g. Daytona's 10GB cap), without ever
283
- touching the workspace, agent outputs, installed tools, or task assets."""
283
+ touching the workspace, agent outputs, installed tools, or task assets.
284
+ Guards PR #669."""
284
285
  from benchflow.sandbox.lockdown import harden_before_verify
285
286
 
286
287
  env = _make_env()
287
288
  await harden_before_verify(env, _make_task(), sandbox_user=None)
288
289
 
289
290
  reclaim = next(
290
- (c for c in env.exec.call_args_list if ".cache/uv" in c.args[0]),
291
+ (c for c in env.exec.call_args_list if "/.cache/" in c.args[0]),
291
292
  None,
292
293
  )
293
294
  assert reclaim is not None, "expected a pre-verifier disk reclaim exec"
294
295
  cmd = reclaim.args[0]
295
- # clears only re-downloadable caches ...
296
- assert ".cache/pip" in cmd and "apt/archives" in cmd
297
- # ... and never the workspace, agent outputs, installed tools, or assets
298
- for forbidden in ("/app", "/workspace", "/tests", "site-packages", ".venv"):
299
- assert forbidden not in cmd, f"reclaim must not touch {forbidden}"
296
+ # clears only re-downloadable caches (uv/pip/apt) ...
297
+ assert "uv_build" in cmd and "pip" in cmd and "apt/archives" in cmd
298
+ # ... guarded against symlinks and the workspace/output roots (#601)
299
+ assert "islink" in cmd and "realpath" in cmd and "/logs" in cmd
300
+ # no active workspace -> the guard placeholder is plumbed through
301
+ assert "/nonexistent" in cmd
300
302
  # best-effort: swallows errors and never aborts hardening
301
303
  assert "2>/dev/null" in cmd and cmd.rstrip().endswith("true")
302
304
  # runs as root so it can clear every user's cache
303
305
  assert reclaim.kwargs.get("user") == "root"
304
306
 
305
307
  @pytest.mark.asyncio
306
- @pytest.mark.parametrize("workspace", ["/root", "/home/agent"])
307
- async def test_reclaim_skips_caches_inside_the_workspace(self, workspace):
308
- """When a task uses /root or /home/<user> as its workspace, the reclaim
309
- must NOT delete "$workspace/.cache" that is workspace state the verifier
310
- must see untouched. restore_workspace defaults False, so nothing would
311
- restore a stray deletion. Guards the workspace-fidelity review blocker."""
308
+ @pytest.mark.parametrize("workspace", ["/root", "/home/agent", "/app"])
309
+ async def test_reclaim_plumbs_workspace_into_guard(self, workspace):
310
+ """The active workspace is passed to the reclaim snippet as argv[1] so
311
+ the realpath overlap guard can protect it. Guards PR #669."""
312
312
  import shlex
313
- import subprocess
314
313
 
315
314
  from benchflow.sandbox.lockdown import harden_before_verify
316
315
 
@@ -320,47 +319,104 @@ class TestVerifierDirWipe:
320
319
  )
321
320
 
322
321
  reclaim = next(
323
- (c for c in env.exec.call_args_list if ".cache/uv" in c.args[0]), None
322
+ (c for c in env.exec.call_args_list if "/.cache/" in c.args[0]), None
324
323
  )
325
324
  assert reclaim is not None
326
- cmd = reclaim.args[0]
327
- # the active workspace is plumbed into the guard ...
328
- assert f"WS={shlex.quote(workspace)}" in cmd
329
- # ... and overlap is guarded in both directions
330
- assert 'case "$WS" in "$u" | "$u"/*) continue' in cmd
331
- assert 'case "$u" in "$WS"/*) continue' in cmd
332
-
333
- # behaviorally: the guard skips the workspace dir itself (hermetic check)
334
- decide = subprocess.run(
335
- [
336
- "sh",
337
- "-c",
338
- f"WS={shlex.quote(workspace)}; u={shlex.quote(workspace)}; "
339
- 'case "$WS" in "$u" | "$u"/*) echo skip; exit ;; esac; '
340
- 'case "$u" in "$WS"/*) echo skip; exit ;; esac; echo clear',
341
- ],
342
- capture_output=True,
343
- text=True,
344
- ).stdout.strip()
345
- assert decide == "skip", (
346
- f"workspace {workspace} should be skipped, got {decide}"
325
+ assert (
326
+ reclaim.args[0]
327
+ .rstrip()
328
+ .endswith(f"{shlex.quote(workspace)} 2>/dev/null; true")
347
329
  )
348
330
 
349
- @pytest.mark.asyncio
350
- async def test_reclaim_still_clears_caches_outside_the_workspace(self):
351
- """A workspace like /app (not under /root or /home) leaves the per-user
352
- cache reclaim fully active."""
353
- from benchflow.sandbox.lockdown import harden_before_verify
331
+ # ── Behavior tests: run the REAL reclaim snippet against a temp root ──────
332
+ # (#601 regression suite — the old string-shape tests passed while the
333
+ # shell command deleted agent state through symlinks and /tmp globs.)
354
334
 
355
- env = _make_env()
356
- await harden_before_verify(
357
- env, _make_task(), sandbox_user=None, workspace="/app"
358
- )
359
- reclaim = next(
360
- (c for c in env.exec.call_args_list if ".cache/uv" in c.args[0]), None
361
- )
362
- assert reclaim is not None
363
- assert "WS=/app" in reclaim.args[0]
335
+ @staticmethod
336
+ def _run_reclaim(workspace: str, prefix) -> None:
337
+ """Execute the production reclaim snippet hermetically under
338
+ ``prefix`` - the same code, same interpreter contract as the sandbox."""
339
+ import subprocess
340
+ import sys
341
+
342
+ from benchflow.sandbox._cache_reclaim import RECLAIM_CACHES_PY
343
+
344
+ result = subprocess.run(
345
+ [sys.executable, "-c", RECLAIM_CACHES_PY, workspace, str(prefix)],
346
+ capture_output=True,
347
+ text=True,
348
+ timeout=60,
349
+ )
350
+ assert result.returncode == 0, result.stderr
351
+
352
+ def test_reclaim_does_not_traverse_cache_symlink_into_workspace(self, tmp_path):
353
+ """Guards PR #669 for issue #601 (repro 1): an agent-planted
354
+ ``~/.cache -> /app`` must not turn the cache delete into
355
+ ``rm -rf /app/uv`` — the old shell form did exactly that."""
356
+ (tmp_path / "app" / "uv").mkdir(parents=True)
357
+ (tmp_path / "app" / "uv" / "state.txt").write_text("agent state")
358
+ (tmp_path / "root").mkdir()
359
+ (tmp_path / "root" / ".cache").symlink_to(tmp_path / "app")
360
+
361
+ self._run_reclaim(str(tmp_path / "app"), tmp_path)
362
+
363
+ assert (tmp_path / "app" / "uv" / "state.txt").exists()
364
+
365
+ def test_reclaim_does_not_traverse_symlink_into_artifacts(self, tmp_path):
366
+ """Guards PR #669 for issue #601 (repro 2): ``.cache -> /logs/artifacts``
367
+ must not delete artifact state — /logs is a protected root even when it
368
+ is not the workspace."""
369
+ (tmp_path / "logs" / "artifacts" / "uv").mkdir(parents=True)
370
+ (tmp_path / "logs" / "artifacts" / "uv" / "state.txt").write_text("x")
371
+ (tmp_path / "home" / "agent").mkdir(parents=True)
372
+ (tmp_path / "home" / "agent" / ".cache").symlink_to(
373
+ tmp_path / "logs" / "artifacts"
374
+ )
375
+
376
+ self._run_reclaim("/nonexistent", tmp_path)
377
+
378
+ assert (tmp_path / "logs" / "artifacts" / "uv" / "state.txt").exists()
379
+
380
+ def test_reclaim_spares_tmp_workspace_matching_glob(self, tmp_path):
381
+ """Guards PR #669 for issue #601 (repro 3): a legitimate
382
+ ``/tmp/uv-workspace`` workspace must survive the ``/tmp/uv-*`` glob —
383
+ the old form deleted it unconditionally."""
384
+ ws = tmp_path / "tmp" / "uv-workspace"
385
+ ws.mkdir(parents=True)
386
+ (ws / "state.txt").write_text("answer")
387
+
388
+ self._run_reclaim(str(ws), tmp_path)
389
+
390
+ assert (ws / "state.txt").exists()
391
+
392
+ def test_reclaim_still_clears_real_caches_outside_workspace(self, tmp_path):
393
+ """Guards PR #669 for issue #601 (repro 4): real, non-symlinked caches
394
+ outside the workspace are still reclaimed, so the ENOSPC mitigation
395
+ the reclaim exists for stays effective."""
396
+ (tmp_path / "root" / ".cache" / "uv" / "blob").mkdir(parents=True)
397
+ (tmp_path / "home" / "u1" / ".cache" / "pip").mkdir(parents=True)
398
+ (tmp_path / "tmp" / "uv-build123").mkdir(parents=True)
399
+ (tmp_path / "var" / "cache" / "apt" / "archives").mkdir(parents=True)
400
+ (tmp_path / "var" / "cache" / "apt" / "archives" / "x.deb").write_text("d")
401
+
402
+ self._run_reclaim(str(tmp_path / "app"), tmp_path)
403
+
404
+ assert not (tmp_path / "root" / ".cache" / "uv").exists()
405
+ assert not (tmp_path / "home" / "u1" / ".cache" / "pip").exists()
406
+ assert not (tmp_path / "tmp" / "uv-build123").exists()
407
+ assert not (tmp_path / "var" / "cache" / "apt" / "archives" / "x.deb").exists()
408
+
409
+ def test_reclaim_skips_caches_inside_the_workspace(self, tmp_path):
410
+ """When a task uses /root as its workspace, "$ws/.cache" is workspace
411
+ state the verifier must see untouched (the pre-#601 guarantee, now
412
+ enforced by realpath overlap instead of string comparison). Guards PR #669."""
413
+ cache = tmp_path / "root" / ".cache" / "uv"
414
+ cache.mkdir(parents=True)
415
+ (cache / "state.txt").write_text("workspace state")
416
+
417
+ self._run_reclaim(str(tmp_path / "root"), tmp_path)
418
+
419
+ assert (cache / "state.txt").exists()
364
420
 
365
421
 
366
422
  # ── TestBuildConfigSnapshot ───────────────────────────────────────────────────
@@ -0,0 +1,161 @@
1
+ """Tests for `bench tasks digest` and task_digest().
2
+
3
+ The digest pins a task's content independent of git and must byte-match the
4
+ reference digests published in the skillsbench dataset registry
5
+ (registry.json / docs/dataset-versioning.md, skillsbench PR #922):
6
+
7
+ task_digest = sha256( for each regular file under the task directory,
8
+ sorted by POSIX relative path:
9
+ update( path_utf8 + b"\\x00"
10
+ + sha256(file_bytes).digest() ) )
11
+
12
+ Symlinks and file modes are excluded; the hex digest is prefixed "sha256:".
13
+
14
+ The implementation was cross-validated against all 87 published digests of
15
+ the skillsbench v1.1 registry entry (git tag v1.1, commit 14f33967) — e.g.
16
+ tasks/3d-scan-calc reproduces
17
+ sha256:587a79f13a31098b5f7b4d1f99582ac6d4184e2cca1f02e2679fc98b81861220.
18
+ The golden values below freeze the same algorithm onto an offline fixture.
19
+ """
20
+
21
+ import hashlib
22
+ from pathlib import Path
23
+
24
+ import pytest
25
+ from typer.testing import CliRunner
26
+
27
+ from benchflow._utils.task_authoring import task_digest
28
+ from benchflow.cli.main import app
29
+
30
+ FIXTURES = Path(__file__).parent / "fixtures" / "digest"
31
+
32
+ # Frozen against the checked-in fixture tasks. If these fail without a
33
+ # deliberate fixture change, the algorithm drifted from the published
34
+ # skillsbench registry digests — fix the code, not the constants.
35
+ ALPHA_DIGEST = "sha256:6a0c023a27c1dd1b68b16ed23a41cdd9b3ea3401599bc6abbb95ed77d41e08f9"
36
+ BETA_DIGEST = "sha256:8f5a4f868cef8c5c50b700183d0b9bd7405d92245eed79a0d50494b7daa0de16"
37
+
38
+
39
+ def _spec_digest(file_entries: list[tuple[str, bytes]]) -> str:
40
+ """The registry construction, written out literally, in the given order."""
41
+ h = hashlib.sha256()
42
+ for rel_posix, content in file_entries:
43
+ h.update(rel_posix.encode("utf-8"))
44
+ h.update(b"\x00")
45
+ h.update(hashlib.sha256(content).digest())
46
+ return f"sha256:{h.hexdigest()}"
47
+
48
+
49
+ class TestTaskDigest:
50
+ def test_golden_fixture_digests(self):
51
+ assert task_digest(FIXTURES / "alpha-task") == ALPHA_DIGEST
52
+ assert task_digest(FIXTURES / "beta-task") == BETA_DIGEST
53
+
54
+ def test_matches_spec_construction(self, tmp_path):
55
+ task = tmp_path / "task"
56
+ (task / "environment").mkdir(parents=True)
57
+ (task / "task.toml").write_bytes(b'version = "1.1"\n')
58
+ (task / "instruction.md").write_bytes(b"do the thing\n")
59
+ (task / "environment" / "Dockerfile").write_bytes(b"FROM scratch\n")
60
+ expected = _spec_digest(
61
+ [
62
+ ("environment/Dockerfile", b"FROM scratch\n"),
63
+ ("instruction.md", b"do the thing\n"),
64
+ ("task.toml", b'version = "1.1"\n'),
65
+ ]
66
+ )
67
+ assert task_digest(task) == expected
68
+
69
+ def test_sorts_by_posix_path_string_not_path_parts(self, tmp_path):
70
+ """'a.b/c' sorts before 'a/c' as a string ('.' < '/'), after it as
71
+ Path parts — the registry orders by the POSIX path string."""
72
+ task = tmp_path / "task"
73
+ (task / "a").mkdir(parents=True)
74
+ (task / "a.b").mkdir()
75
+ (task / "a" / "c").write_bytes(b"slash")
76
+ (task / "a.b" / "c").write_bytes(b"dot")
77
+ string_order = _spec_digest([("a.b/c", b"dot"), ("a/c", b"slash")])
78
+ parts_order = _spec_digest([("a/c", b"slash"), ("a.b/c", b"dot")])
79
+ assert string_order != parts_order
80
+ assert task_digest(task) == string_order
81
+
82
+ def test_symlinks_excluded(self, tmp_path):
83
+ task = tmp_path / "task"
84
+ task.mkdir()
85
+ (task / "task.toml").write_bytes(b'version = "1.1"\n')
86
+ before = task_digest(task)
87
+
88
+ outside = tmp_path / "outside"
89
+ outside.mkdir()
90
+ (outside / "payload.txt").write_bytes(b"must not be hashed")
91
+ (task / "file-link.toml").symlink_to(task / "task.toml")
92
+ (task / "dir-link").symlink_to(outside, target_is_directory=True)
93
+ (task / "broken-link").symlink_to(task / "does-not-exist")
94
+
95
+ assert task_digest(task) == before
96
+
97
+ def test_file_modes_excluded(self, tmp_path):
98
+ task = tmp_path / "task"
99
+ task.mkdir()
100
+ script = task / "solve.sh"
101
+ script.write_bytes(b"#!/bin/bash\n")
102
+ before = task_digest(task)
103
+ script.chmod(0o755)
104
+ assert task_digest(task) == before
105
+
106
+ def test_empty_directories_excluded(self, tmp_path):
107
+ task = tmp_path / "task"
108
+ task.mkdir()
109
+ (task / "task.toml").write_bytes(b'version = "1.1"\n')
110
+ before = task_digest(task)
111
+ (task / "empty" / "nested").mkdir(parents=True)
112
+ assert task_digest(task) == before
113
+
114
+ def test_sensitive_to_content_and_path(self, tmp_path):
115
+ task = tmp_path / "task"
116
+ task.mkdir()
117
+ f = task / "task.toml"
118
+ f.write_bytes(b'version = "1.1"\n')
119
+ original = task_digest(task)
120
+
121
+ f.write_bytes(b'version = "1.2"\n')
122
+ changed_content = task_digest(task)
123
+ assert changed_content != original
124
+
125
+ f.write_bytes(b'version = "1.1"\n')
126
+ f.rename(task / "renamed.toml")
127
+ assert task_digest(task) not in (original, changed_content)
128
+
129
+ def test_not_a_directory_raises(self, tmp_path):
130
+ with pytest.raises(NotADirectoryError):
131
+ task_digest(tmp_path / "missing")
132
+
133
+
134
+ class TestTasksDigestCli:
135
+ def test_single_task_prints_digest(self):
136
+ result = CliRunner().invoke(
137
+ app, ["tasks", "digest", str(FIXTURES / "alpha-task")]
138
+ )
139
+ assert result.exit_code == 0
140
+ assert result.output == f"{ALPHA_DIGEST}\n"
141
+
142
+ def test_directory_of_tasks_prints_name_digest_lines(self):
143
+ """One '<name> <digest>' line per task, sorted; non-task entries
144
+ (not-a-task/, README.md) are skipped."""
145
+ result = CliRunner().invoke(app, ["tasks", "digest", str(FIXTURES)])
146
+ assert result.exit_code == 0
147
+ assert result.output.splitlines() == [
148
+ f"alpha-task {ALPHA_DIGEST}",
149
+ f"beta-task {BETA_DIGEST}",
150
+ ]
151
+
152
+ def test_missing_path_fails(self, tmp_path):
153
+ result = CliRunner().invoke(app, ["tasks", "digest", str(tmp_path / "nope")])
154
+ assert result.exit_code == 1
155
+
156
+ def test_directory_without_tasks_fails(self):
157
+ result = CliRunner().invoke(
158
+ app, ["tasks", "digest", str(FIXTURES / "not-a-task")]
159
+ )
160
+ assert result.exit_code == 1
161
+ assert "No tasks under" in result.output
File without changes