code-sandboxes 1.9.20__tar.gz → 1.9.22__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 (263) hide show
  1. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/daytona.py +85 -1
  4. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/managed.py +18 -1
  5. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/attest.py +30 -1
  6. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/conformance.py +31 -3
  7. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_attest.py +54 -15
  8. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_daytona_builder.py +96 -0
  9. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_managed_builders.py +5 -4
  10. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/build.yml +0 -0
  11. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/environments-live.yml +0 -0
  12. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/py-code-style.yml +0 -0
  13. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/py-tests.yml +0 -0
  14. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/py-typing.yml +0 -0
  15. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.github/workflows/reusable-python.yml +0 -0
  16. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.gitignore +0 -0
  17. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.licenserc.yaml +0 -0
  18. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.pre-commit-config.yaml +0 -0
  19. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/.vscode/settings.json +0 -0
  20. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/CHANGELOG.md +0 -0
  21. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/Dockerfile +0 -0
  22. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/LICENSE +0 -0
  23. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/Makefile +0 -0
  24. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/README.md +0 -0
  25. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/__init__.py +0 -0
  26. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/base.py +0 -0
  27. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/bridge_mount.py +0 -0
  28. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/builds.py +0 -0
  29. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/cli.py +0 -0
  30. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/client.py +0 -0
  31. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/cloudflare_sandbox.py +0 -0
  32. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/commands.py +0 -0
  33. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/console.py +0 -0
  34. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/contents.py +0 -0
  35. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/coreweave_sandbox.py +0 -0
  36. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/datalayer_sandbox.py +0 -0
  37. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/daytona_sandbox.py +0 -0
  38. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/docker_sandbox.py +0 -0
  39. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/e2b_sandbox.py +0 -0
  40. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/__init__.py +0 -0
  41. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/__main__.py +0 -0
  42. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/accounts.py +0 -0
  43. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/__init__.py +0 -0
  44. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/datalayer.py +0 -0
  45. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/e2b.py +0 -0
  46. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/adapters/modal.py +0 -0
  47. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/bases.py +0 -0
  48. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/breaker.py +0 -0
  49. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/build_secrets.py +0 -0
  50. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/builders.py +0 -0
  51. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/canonical.py +0 -0
  52. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  53. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  54. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  55. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/contract.py +0 -0
  56. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/doctor/__init__.py +0 -0
  57. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/doctor/build.py +0 -0
  58. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  59. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  60. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/errors.py +0 -0
  61. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/files.py +0 -0
  62. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/image_import.py +0 -0
  63. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/lifecycle.py +0 -0
  64. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/policy.py +0 -0
  65. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/redact.py +0 -0
  66. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/resolve.py +0 -0
  67. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/resolve_conda.py +0 -0
  68. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/schema.py +0 -0
  69. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/environments/spec.py +0 -0
  70. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/eval_sandbox.py +0 -0
  71. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/exceptions.py +0 -0
  72. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/filesystem.py +0 -0
  73. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/google_colab.py +0 -0
  74. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/google_colab_sandbox.py +0 -0
  75. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/interfaces.py +0 -0
  76. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/jupyter_ingress.py +0 -0
  77. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  78. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/kaggle.py +0 -0
  79. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/kaggle_execute.py +0 -0
  80. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/kaggle_live.py +0 -0
  81. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/kaggle_sandbox.py +0 -0
  82. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/lifecycle.py +0 -0
  83. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/manage.py +0 -0
  84. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/modal_sandbox.py +0 -0
  85. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/models.py +0 -0
  86. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/monty_sandbox.py +0 -0
  87. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/provider_ingress.py +0 -0
  88. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/code_sandboxes/providers.py +0 -0
  89. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docker/Dockerfile +0 -0
  90. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/.gitignore +0 -0
  91. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/LICENSE +0 -0
  92. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/Makefile +0 -0
  93. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/README.md +0 -0
  94. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/babel.config.js +0 -0
  95. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/api-reference/_category_.yml +0 -0
  96. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/api-reference/index.mdx +0 -0
  97. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/cli/_category_.yml +0 -0
  98. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/cli/index.mdx +0 -0
  99. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/contribute/_category_.yml +0 -0
  100. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/contribute/index.mdx +0 -0
  101. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/environments/_category_.yml +0 -0
  102. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/environments/contract.mdx +0 -0
  103. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/environments/index.mdx +0 -0
  104. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/environments/specification.mdx +0 -0
  105. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/environments/spike-2026-09.mdx +0 -0
  106. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/examples/_category_.yml +0 -0
  107. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/examples/index.mdx +0 -0
  108. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/guide/_category_.yml +0 -0
  109. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/guide/index.mdx +0 -0
  110. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/index.mdx +0 -0
  111. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/install/_category_.yml +0 -0
  112. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/install/index.mdx +0 -0
  113. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/provider-ingress.mdx +0 -0
  114. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/_category_.yml +0 -0
  115. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/cloudflare.mdx +0 -0
  116. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/coreweave.mdx +0 -0
  117. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/datalayer.mdx +0 -0
  118. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/daytona.mdx +0 -0
  119. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/docker.mdx +0 -0
  120. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/e2b.mdx +0 -0
  121. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/eval.mdx +0 -0
  122. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/google-colab.mdx +0 -0
  123. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/index.mdx +0 -0
  124. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/jupyter-server.mdx +0 -0
  125. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/kaggle.mdx +0 -0
  126. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/modal.mdx +0 -0
  127. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docs/providers/monty.mdx +0 -0
  128. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/docusaurus.config.js +0 -0
  129. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/package.json +0 -0
  130. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/sidebars.js +0 -0
  131. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow1.js +0 -0
  132. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow1.module.css +0 -0
  133. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow2.js +0 -0
  134. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow2.module.css +0 -0
  135. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow3.js +0 -0
  136. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/components/HomepageRow3.module.css +0 -0
  137. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/css/custom.css +0 -0
  138. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/pages/index.module.css +0 -0
  139. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/pages/markdown-page.md +0 -0
  140. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/theme/CustomDocItem.tsx +0 -0
  141. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/src/theme/IconsTitle.tsx +0 -0
  142. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/bricks.svg +0 -0
  143. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/building-construction.svg +0 -0
  144. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/cloud-sun.svg +0 -0
  145. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/cloud.svg +0 -0
  146. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/collaboration.svg +0 -0
  147. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/copy-left.svg +0 -0
  148. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/logo.png +0 -0
  149. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/logo.svg +0 -0
  150. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/personas/business.svg +0 -0
  151. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  152. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/personas/devops.svg +0 -0
  153. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/datalayer/project/project-line.svg +0 -0
  154. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/favicon.ico +0 -0
  155. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/jupyter.svg +0 -0
  156. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/open-source.svg +0 -0
  157. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/react-js.svg +0 -0
  158. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/react-js2.svg +0 -0
  159. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/ringed-planet.svg +0 -0
  160. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/robot.svg +0 -0
  161. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/rocket.svg +0 -0
  162. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/shield.svg +0 -0
  163. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/sparkles.svg +0 -0
  164. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/streamlit.gif +0 -0
  165. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/super-hero.svg +0 -0
  166. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/target.svg +0 -0
  167. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/docs/static/img/thumbs-up.svg +0 -0
  168. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/README.md +0 -0
  169. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/Makefile +0 -0
  170. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/cloudflare_sandbox_example.py +0 -0
  171. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/coreweave_sandbox_example.py +0 -0
  172. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/datalayer_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/daytona_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/docker_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/e2b_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/eval_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/google_colab_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/kaggle_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/modal_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/exec/monty_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/Makefile +0 -0
  183. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/cloudflare_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/coreweave_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/datalayer_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/daytona_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/docker_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/e2b_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/eval_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/google_colab_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/kaggle_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/modal_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/examples/repl/monty_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/pyproject.toml +0 -0
  196. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/schemas/environment-v1alpha1.json +0 -0
  197. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/__init__.py +0 -0
  198. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/conftest.py +0 -0
  199. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  200. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_bridge_mount.py +0 -0
  201. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_builds.py +0 -0
  202. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_cli_exec.py +0 -0
  203. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_cli_repl.py +0 -0
  204. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_client.py +0 -0
  205. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_client_contents.py +0 -0
  206. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_client_files.py +0 -0
  207. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_client_outcome_error_output.py +0 -0
  208. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_cloudflare.py +0 -0
  209. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_commands_envelope.py +0 -0
  210. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_console.py +0 -0
  211. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_contents_contract.py +0 -0
  212. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_contents_live_matrix.py +0 -0
  213. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_coreweave.py +0 -0
  214. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_datalayer_sandbox.py +0 -0
  215. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_daytona.py +0 -0
  216. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_e2b.py +0 -0
  217. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_accounts.py +0 -0
  218. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_bases.py +0 -0
  219. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_breaker.py +0 -0
  220. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_build_secrets.py +0 -0
  221. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_builders.py +0 -0
  222. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_canonical.py +0 -0
  223. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_conformance.py +0 -0
  224. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_constraints.py +0 -0
  225. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_contract.py +0 -0
  226. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_cross_variant_report.py +0 -0
  227. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_datalayer_builder.py +0 -0
  228. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_doctor.py +0 -0
  229. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_e2b_builder.py +0 -0
  230. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_errors.py +0 -0
  231. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_image_import.py +0 -0
  232. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_lifecycle.py +0 -0
  233. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_live_matrix.py +0 -0
  234. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_modal_builder.py +0 -0
  235. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_redact.py +0 -0
  236. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_resolve.py +0 -0
  237. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_resolve_conda.py +0 -0
  238. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_environment_spec.py +0 -0
  239. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_eval.py +0 -0
  240. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  241. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_factory.py +0 -0
  242. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_google_colab.py +0 -0
  243. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_integration.py +0 -0
  244. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_jupyter_ingress.py +0 -0
  245. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_jupyter_server.py +0 -0
  246. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_jupyter_server_streaming.py +0 -0
  247. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_kaggle.py +0 -0
  248. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_kaggle_execute.py +0 -0
  249. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_kaggle_live.py +0 -0
  250. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_kaggle_session.py +0 -0
  251. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_kernel_client_compatibility.py +0 -0
  252. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_lifecycle_vocabulary.py +0 -0
  253. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_manage.py +0 -0
  254. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_manage_datalayer.py +0 -0
  255. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_modal_google_colab_sandbox.py +0 -0
  256. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_modal_session.py +0 -0
  257. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_models.py +0 -0
  258. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_output_line_fidelity.py +0 -0
  259. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_provider_catalog_names.py +0 -0
  260. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_provider_ingress.py +0 -0
  261. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_providers.py +0 -0
  262. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_sandboxes.py +0 -0
  263. {code_sandboxes-1.9.20 → code_sandboxes-1.9.22}/tests/test_the_kill_relaunch_drill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.9.20
3
+ Version: 1.9.22
4
4
  Project-URL: Home, https://github.com/datalayer/code-sandboxes
5
5
  Author-email: Datalayer <info@datalayer.io>
6
6
  License: BSD 3-Clause License
@@ -3,4 +3,4 @@
3
3
 
4
4
  """Code Sandboxes."""
5
5
 
6
- __version__ = "1.9.20"
6
+ __version__ = "1.9.22"
@@ -90,7 +90,7 @@ from __future__ import annotations
90
90
  import shlex
91
91
  import tempfile
92
92
  import uuid
93
- from collections.abc import Callable
93
+ from collections.abc import Callable, Sequence
94
94
  from pathlib import Path
95
95
  from typing import Any
96
96
 
@@ -100,6 +100,7 @@ from ..builders import (
100
100
  ArtifactReference,
101
101
  BuildRequest,
102
102
  CapabilityFinding,
103
+ ValidationResult,
103
104
  )
104
105
  from ..contract import SANDBOX_CONTRACT_V1
105
106
  from ..errors import (
@@ -567,6 +568,89 @@ class Builder(ManagedBuilder):
567
568
  raise self._provider_error("ask whether the snapshot exists", error) from error
568
569
  return True
569
570
 
571
+ def smoke_test(
572
+ self,
573
+ artifact: ArtifactReference,
574
+ *,
575
+ environment: Any = None,
576
+ lock_text: str | None = None,
577
+ secret_values: Sequence[str] = (),
578
+ ) -> ValidationResult:
579
+ """Launch the snapshot and run Appendix B's core tier in it (E2-04).
580
+
581
+ This box's own `Done when` asks for exactly this — "a sandbox launched
582
+ from its id passes the core tier" — and until 2026-09-17 it refused
583
+ through `ManagedBuilder`, so **no Daytona build could reach
584
+ `succeeded`**: the workflow calls this step, the snapshot was built and
585
+ live at the provider, and the build was recorded failed.
586
+
587
+ **Launched by id, never by name** (E0-04): a Daytona sandbox record
588
+ keeps the snapshot's *name*, and a name is republished, so only the id
589
+ says which artifact actually ran.
590
+
591
+ **Restarted by stopping and starting the sandbox**, not the kernel
592
+ (E0-04 again): Daytona's own daemon is PID 1 here, so there is no
593
+ kernel to restart, and check 8 means the thing that survives a real
594
+ restart.
595
+
596
+ The sandbox is deleted whether the tier passed or not — a smoke test
597
+ that leaves a sandbox running bills the owner for a check.
598
+ """
599
+ if environment is None:
600
+ raise EnvironmentsError(
601
+ CAPABILITY_UNSUPPORTED,
602
+ "A Daytona smoke test needs the version's spec: the core tier "
603
+ "asks for the Python version it declared and the packages its "
604
+ "lock pinned, and an artifact carries neither",
605
+ detail={"variant": self.variant},
606
+ )
607
+ from ..conformance import expected_packages, run_core_tier
608
+
609
+ snapshot = artifact.provider_artifact_id or artifact.immutable_reference
610
+ sandbox = self._smoke_test_sandbox(snapshot)
611
+ self._log(f"Launching {snapshot} to smoke-test it")
612
+ try:
613
+ sandbox.start()
614
+ return run_core_tier(
615
+ sandbox,
616
+ python_version=environment.spec.language.version,
617
+ expected_packages=expected_packages(environment, lock_text or ""),
618
+ secret_values=tuple(secret_values),
619
+ restart=lambda: self._restart(sandbox),
620
+ )
621
+ except EnvironmentsError:
622
+ raise
623
+ except Exception as error:
624
+ raise self._provider_error("smoke-test the snapshot", error) from error
625
+ finally:
626
+ try:
627
+ sandbox.stop()
628
+ except Exception as error:
629
+ self._log(f"The smoke-test sandbox could not be stopped: {error}")
630
+
631
+ def _smoke_test_sandbox(self, snapshot: str) -> Any:
632
+ """A sandbox of this build's own snapshot, deleted when it stops."""
633
+ from ...daytona_sandbox import DaytonaSandbox
634
+
635
+ secrets = self._provider_secrets()
636
+ return DaytonaSandbox(
637
+ api_key=secrets.get("DAYTONA_API_KEY"),
638
+ organization_id=secrets.get("DAYTONA_ORGANIZATION_ID"),
639
+ snapshot=snapshot,
640
+ delete_on_stop=True,
641
+ )
642
+
643
+ @staticmethod
644
+ def _restart(sandbox: Any) -> None:
645
+ """Check 8's restart, as Daytona can do it.
646
+
647
+ Its daemon is PID 1, so there is no kernel to restart: the sandbox
648
+ itself is stopped and started, which is the stronger version of the
649
+ same question.
650
+ """
651
+ sandbox.stop()
652
+ sandbox.start()
653
+
570
654
  def _provider_error(self, what: str, error: BaseException) -> EnvironmentsError:
571
655
  return EnvironmentsError(
572
656
  PROVIDER_ERROR,
@@ -24,6 +24,7 @@ An SDK imported at module scope would turn `validate` into a 500.
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
+ from collections.abc import Sequence
27
28
  from typing import Any, Callable
28
29
 
29
30
  from ..builders import (
@@ -261,7 +262,23 @@ class ManagedBuilder:
261
262
  def inspect(self, artifact: ArtifactReference) -> ArtifactMetadata:
262
263
  raise self._not_built("inspect an artifact")
263
264
 
264
- def smoke_test(self, artifact: ArtifactReference) -> ValidationResult:
265
+ def smoke_test(
266
+ self,
267
+ artifact: ArtifactReference,
268
+ *,
269
+ environment: Any = None,
270
+ lock_text: str | None = None,
271
+ secret_values: Sequence[str] = (),
272
+ ) -> ValidationResult:
273
+ """Launch the artifact and run Appendix B's core tier in it (E2-03/04/05).
274
+
275
+ `environment` and `lock_text` are what the core tier needs and an
276
+ artifact does not carry: the Python version the spec asks for and the
277
+ packages its lock pins. The caller has both — it read them to build —
278
+ and passes them rather than making every adapter fetch them again.
279
+ They are keyword-only and optional so a caller that has neither still
280
+ type-checks, and an adapter that needs them says so itself.
281
+ """
265
282
  raise self._not_built("smoke-test an artifact")
266
283
 
267
284
  def resolve(self, version_ref: str) -> ArtifactReference:
@@ -675,14 +675,43 @@ def attest_artifact(
675
675
  answers the mapping the workflow stores: the scan's decision, the
676
676
  signature, the SBOM and provenance references, and the size.
677
677
  """
678
+ variant = str(getattr(artifact, "variant", "") or "")
678
679
  reference = str(getattr(artifact, "immutable_reference", "") or "")
680
+ if variant and variant != "datalayer":
681
+ # D-11 is about the Datalayer artifact: it lives in this platform's
682
+ # registry, the scanner reads it there, cosign signs that digest, and
683
+ # the Operator refuses to start what is unsigned. A managed artifact
684
+ # is none of those things — it lives in the owner's own provider
685
+ # account (D-8) and is referenced the way that provider names it: a
686
+ # Daytona snapshot uuid, an E2B build id, a Modal `im-…`. There is no
687
+ # digest in a repository to scan or sign, and no Operator starting it.
688
+ #
689
+ # Attesting one anyway is what the first real Daytona build did, and
690
+ # it failed *after* the snapshot was built — the work done, the
691
+ # artifact live at the provider, and the build recorded as failed
692
+ # (2026-09-17).
693
+ #
694
+ # Publishing is not weakened by this: E2-15's gate requires the
695
+ # **Datalayer** artifact to have passed its scan, and that one is
696
+ # still attested here.
697
+ if log:
698
+ log(f"{variant} artifacts are not attested: {reference} is not a digest in a registry")
699
+ return {
700
+ "scan_summary": {},
701
+ "sbom_ref": "",
702
+ "provenance_ref": "",
703
+ "signature_ref": "",
704
+ "size_bytes": size_bytes,
705
+ "signed_now": False,
706
+ "licenses": [],
707
+ }
679
708
  registry, _, rest = reference.partition("/")
680
709
  repository, _, digest = rest.partition("@")
681
710
  if not (registry and repository and _DIGEST.match(digest)):
682
711
  raise EnvironmentsError(
683
712
  PROVIDER_ERROR,
684
713
  f"`{reference}` is not a digest in a repository, so it cannot be attested",
685
- detail={"reference": reference},
714
+ detail={"reference": reference, "variant": variant},
686
715
  )
687
716
  use = attestor or Attestor(
688
717
  key=str(getattr(credential, "signing_key", "") or ""),
@@ -457,6 +457,36 @@ def _secrets(
457
457
  )
458
458
 
459
459
 
460
+ def expected_packages(environment: Any, lock_text: str) -> dict[str, str]:
461
+ """Each top-level distribution the spec names, pinned to what its lock resolved.
462
+
463
+ What Appendix B check 5 is given. Never every package the lock pins — most
464
+ of a real lock is transitive, and check 5 tries to *import* each name it is
465
+ handed; a build tool or a C-library-only wheel that was never meant to be
466
+ imported directly would fail a check with nothing wrong to report. The
467
+ spec's own `packages.python.dependencies` is what a person declared
468
+ wanting, so it is what disagreeing across variants (E2-08) means something
469
+ about.
470
+
471
+ Here rather than in the durable worker, which had the only copy, so that a
472
+ builder running its own smoke test asks the same question of its own
473
+ artifact (E2-03/04/05).
474
+ """
475
+ from packaging.requirements import InvalidRequirement, Requirement
476
+ from packaging.utils import canonicalize_name
477
+
478
+ from .resolve import locked_versions
479
+
480
+ pinned = locked_versions(lock_text) if lock_text else {}
481
+ names: list[str] = []
482
+ for text in environment.spec.packages.python.dependencies:
483
+ try:
484
+ names.append(canonicalize_name(Requirement(text).name))
485
+ except InvalidRequirement:
486
+ continue
487
+ return {name: pinned[name] for name in names if name in pinned}
488
+
489
+
460
490
  def run_core_tier(
461
491
  sandbox: Sandbox,
462
492
  *,
@@ -551,9 +581,7 @@ def _gpu(sandbox: Sandbox, requested: bool, cuda: str | None, timeout: float | N
551
581
  # version its spec describes. A version that asked for none never
552
582
  # reaches here (the trivial pass above), so the extended tier still
553
583
  # gates nothing for a CPU version.
554
- return _result(
555
- 11, not problems, gating=True, detail="; ".join(problems) or None, actual=answer
556
- )
584
+ return _result(11, not problems, gating=True, detail="; ".join(problems) or None, actual=answer)
557
585
 
558
586
 
559
587
  def _throughput(
@@ -19,12 +19,13 @@ from __future__ import annotations
19
19
 
20
20
  import json
21
21
  import subprocess
22
+ from types import SimpleNamespace
22
23
 
23
24
  import pytest
24
25
 
25
26
  from code_sandboxes.environments.attest import Attestor, attest_artifact, licenses_of, signature_tag
26
27
  from code_sandboxes.environments.builders import ArtifactReference
27
- from code_sandboxes.environments.errors import EnvironmentsError
28
+ from code_sandboxes.environments.errors import PROVIDER_ERROR, EnvironmentsError
28
29
  from code_sandboxes.environments.policy import (
29
30
  DEFAULT_POLICY,
30
31
  Finding,
@@ -613,27 +614,25 @@ class TestAttestingAnArtifact:
613
614
  assert cosign.argv == [], "a blocked artifact must not be signed"
614
615
 
615
616
  def test_a_reference_that_is_not_a_digest_cannot_be_attested(self) -> None:
616
- artifact = ArtifactReference(
617
- variant="modal",
618
- immutable_reference="im-1234567890",
619
- provider_artifact_id="im-1234567890",
620
- contract_version="sandbox-contract/v1",
621
- )
617
+ """A `datalayer` artifact must be a digest in this platform's registry.
618
+
619
+ `attest_artifact` takes `artifact: Any`, so nothing guarantees every
620
+ caller went through the model validator that would have refused this.
621
+ """
622
+ artifact = SimpleNamespace(variant="datalayer", immutable_reference="im-1234567890")
622
623
  with pytest.raises(EnvironmentsError) as raised:
623
624
  attest_artifact(artifact=artifact, attestor=an_attestor())
624
625
  assert raised.value.code.code == "DL_ENV_PROVIDER_ERROR"
625
626
 
626
627
  def test_a_malformed_digest_cannot_be_attested_either(self) -> None:
627
628
  """`sha256:bad` starts with `sha256:` too: only a whole one is
628
- accepted (found on PR #27's Copilot review). `e2b` rather than
629
- `datalayer`, whose own model validator already refuses a malformed
630
- digest before this function ever sees it — `attest_artifact` takes
631
- `artifact: Any`, so nothing guarantees every caller went through it."""
632
- artifact = ArtifactReference(
633
- variant="e2b",
629
+ accepted (found on PR #27's Copilot review). Asked of `datalayer`,
630
+ since that is the variant this check is for — `attest_artifact` takes
631
+ `artifact: Any`, so nothing guarantees every caller went through the
632
+ model validator that would have refused it."""
633
+ artifact = SimpleNamespace(
634
+ variant="datalayer",
634
635
  immutable_reference=f"{REGISTRY}/{REPOSITORY}@sha256:bad",
635
- provider_artifact_id="sha256:bad",
636
- contract_version="sandbox-contract/v1",
637
636
  )
638
637
  with pytest.raises(EnvironmentsError) as raised:
639
638
  attest_artifact(artifact=artifact, attestor=an_attestor())
@@ -749,3 +748,43 @@ class TestLicencesFromTheSbom:
749
748
  """Never a reason to fail a build: a licence list is worth having, not dying for."""
750
749
  for document in (None, {}, {"packages": None}, {"components": [1, 2]}, "spdx"):
751
750
  assert licenses_of(document) == []
751
+
752
+
753
+ class TestWhatIsAttestedAndWhatIsNot:
754
+ """D-11 is about the Datalayer artifact, not every artifact.
755
+
756
+ It lives in this platform's registry: the scanner reads it there, cosign
757
+ signs that digest, and the Operator refuses to start what is unsigned. A
758
+ managed artifact is none of those things — it lives in the owner's own
759
+ provider account (D-8), named the way that provider names it.
760
+ """
761
+
762
+ def _artifact(self, variant: str, reference: str):
763
+ return SimpleNamespace(variant=variant, immutable_reference=reference)
764
+
765
+ def test_a_daytona_snapshot_is_not_attested(self) -> None:
766
+ """Its reference is a uuid, and attesting it anyway failed the first
767
+ real Daytona build *after* the snapshot was already built
768
+ (2026-09-17)."""
769
+ answer = attest_artifact(
770
+ artifact=self._artifact("daytona", "51d10ab0-d98d-4117-bdb5-918e98646c92"),
771
+ size_bytes=1234,
772
+ )
773
+ assert answer["scan_summary"] == {}
774
+ assert answer["signature_ref"] == ""
775
+ assert answer["signed_now"] is False
776
+ # What the provider told us is still recorded.
777
+ assert answer["size_bytes"] == 1234
778
+
779
+ def test_an_e2b_build_id_and_a_modal_image_id_are_not_either(self) -> None:
780
+ for variant, reference in (("e2b", "bld-123"), ("modal", "im-abc123")):
781
+ answer = attest_artifact(artifact=self._artifact(variant, reference))
782
+ assert answer["signed_now"] is False, variant
783
+ assert answer["scan_summary"] == {}, variant
784
+
785
+ def test_a_datalayer_artifact_that_is_not_a_digest_still_fails(self) -> None:
786
+ """The check that matters is kept where it means something."""
787
+ with pytest.raises(EnvironmentsError) as raised:
788
+ attest_artifact(artifact=self._artifact("datalayer", "not-a-digest"))
789
+ assert raised.value.code is PROVIDER_ERROR
790
+ assert "cannot be attested" in str(raised.value)
@@ -833,3 +833,99 @@ class TestReadingTheRegistry:
833
833
  builder.build(a_request())
834
834
  builder.exists(an_artifact(provider_artifact_id="snp-999"))
835
835
  assert len(daytona.daytona_calls) == 1
836
+
837
+
838
+ class TestSmokeTestingASnapshot:
839
+ """E2-04's own `Done when`: a sandbox launched from its id passes the core tier.
840
+
841
+ It refused through `ManagedBuilder` until 2026-09-17, and the build
842
+ workflow calls this step — so no Daytona build could reach `succeeded`:
843
+ the snapshot was built and live at the provider, and the build was
844
+ recorded failed.
845
+ """
846
+
847
+ def _environment(self):
848
+ return parse_environment(a_request().environment.model_dump(by_alias=True))
849
+
850
+ def test_it_launches_by_id_and_runs_the_core_tier(self, monkeypatch) -> None:
851
+ """A Daytona sandbox record keeps the snapshot's *name*, and a name is
852
+ republished, so only the id says which artifact ran (E0-04)."""
853
+ made: dict = {}
854
+ ran: dict = {}
855
+
856
+ class FakeSandbox:
857
+ def __init__(self, **kwargs):
858
+ made.update(kwargs)
859
+ self.events: list[str] = []
860
+
861
+ def start(self):
862
+ self.events.append("start")
863
+
864
+ def stop(self):
865
+ self.events.append("stop")
866
+
867
+ builder = a_builder()
868
+ monkeypatch.setattr(
869
+ "code_sandboxes.daytona_sandbox.DaytonaSandbox", FakeSandbox, raising=False
870
+ )
871
+ monkeypatch.setattr(
872
+ "code_sandboxes.environments.conformance.run_core_tier",
873
+ lambda sandbox, **kwargs: ran.update(kwargs) or "the-result",
874
+ )
875
+
876
+ answer = builder.smoke_test(
877
+ an_artifact(
878
+ variant="daytona",
879
+ immutable_reference="snap-1",
880
+ provider_artifact_id="snap-1",
881
+ ),
882
+ environment=self._environment(),
883
+ lock_text="",
884
+ )
885
+ assert answer == "the-result"
886
+ assert made["snapshot"] == "snap-1"
887
+ # A smoke test that leaves a sandbox running bills the owner for a check.
888
+ assert made["delete_on_stop"] is True
889
+ assert "restart" in ran and ran["python_version"]
890
+
891
+ def test_the_sandbox_is_deleted_even_when_the_tier_raises(self, monkeypatch) -> None:
892
+ events: list[str] = []
893
+
894
+ class FakeSandbox:
895
+ def __init__(self, **_kwargs):
896
+ pass
897
+
898
+ def start(self):
899
+ events.append("start")
900
+
901
+ def stop(self):
902
+ events.append("stop")
903
+
904
+ monkeypatch.setattr(
905
+ "code_sandboxes.daytona_sandbox.DaytonaSandbox", FakeSandbox, raising=False
906
+ )
907
+ monkeypatch.setattr(
908
+ "code_sandboxes.environments.conformance.run_core_tier",
909
+ lambda *_a, **_k: (_ for _ in ()).throw(RuntimeError("the tier blew up")),
910
+ )
911
+ with pytest.raises(EnvironmentsError):
912
+ a_builder().smoke_test(
913
+ an_artifact(
914
+ variant="daytona", immutable_reference="snap-1", provider_artifact_id="snap-1"
915
+ ),
916
+ environment=self._environment(),
917
+ lock_text="",
918
+ )
919
+ assert events == ["start", "stop"]
920
+
921
+ def test_without_a_spec_it_says_what_it_needs(self) -> None:
922
+ """The core tier asks for the Python version and the pinned packages,
923
+ and an artifact carries neither."""
924
+ with pytest.raises(EnvironmentsError) as raised:
925
+ a_builder().smoke_test(
926
+ an_artifact(
927
+ variant="daytona", immutable_reference="snap-1", provider_artifact_id="snap-1"
928
+ )
929
+ )
930
+ assert raised.value.code is CAPABILITY_UNSUPPORTED
931
+ assert "needs the version's spec" in str(raised.value)
@@ -379,12 +379,13 @@ class TestTheHalfThatIsNotBuiltYet:
379
379
  assert raised.value.detail["operation"], f"e2b.{operation}"
380
380
 
381
381
  def test_daytona_still_refuses_what_e2_04_did_not_build(self) -> None:
382
- """`build`/`inspect`/`exists` are E2-04's; `smoke_test`, `resolve` and
383
- `delete` are not — see test_environment_daytona_builder.py for what
384
- is built."""
382
+ """`build`, `inspect`, `exists` and now `smoke_test` are E2-04's —
383
+ the last of them because this box's own `Done when` asks for "a
384
+ sandbox launched from its id passes the core tier", and until it was
385
+ built no Daytona build could reach `succeeded`. `resolve` and `delete`
386
+ are still not built — see test_environment_daytona_builder.py."""
385
387
  builder = get_builder("daytona")
386
388
  calls = {
387
- "smoke_test": lambda: builder.smoke_test(None), # type: ignore[arg-type]
388
389
  "resolve": lambda: builder.resolve("geo@1"),
389
390
  "delete": lambda: builder.delete(None), # type: ignore[arg-type]
390
391
  }
File without changes