code-sandboxes 1.9.34__tar.gz → 1.9.35__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 (266) hide show
  1. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/environments-live.yml +98 -20
  2. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/PKG-INFO +1 -1
  3. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/__version__.py +1 -1
  4. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/modal.py +64 -25
  5. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/pyproject.toml +4 -0
  6. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_builders.py +4 -4
  7. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_contract.py +1 -3
  8. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_live_matrix.py +5 -18
  9. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_modal_builder.py +88 -10
  10. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/build.yml +0 -0
  11. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-code-style.yml +0 -0
  12. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-tests.yml +0 -0
  13. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/py-typing.yml +0 -0
  14. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.github/workflows/reusable-python.yml +0 -0
  15. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.gitignore +0 -0
  16. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.licenserc.yaml +0 -0
  17. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.pre-commit-config.yaml +0 -0
  18. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/.vscode/settings.json +0 -0
  19. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/CHANGELOG.md +0 -0
  20. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/Dockerfile +0 -0
  21. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/LICENSE +0 -0
  22. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/Makefile +0 -0
  23. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/README.md +0 -0
  24. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/__init__.py +0 -0
  25. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/base.py +0 -0
  26. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/bridge_mount.py +0 -0
  27. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/builds.py +0 -0
  28. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/cli.py +0 -0
  29. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/client.py +0 -0
  30. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/cloudflare_sandbox.py +0 -0
  31. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/commands.py +0 -0
  32. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/console.py +0 -0
  33. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/contents.py +0 -0
  34. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/coreweave_sandbox.py +0 -0
  35. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/datalayer_sandbox.py +0 -0
  36. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/daytona_sandbox.py +0 -0
  37. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/docker_sandbox.py +0 -0
  38. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/e2b_sandbox.py +0 -0
  39. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/__init__.py +0 -0
  40. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/__main__.py +0 -0
  41. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/accounts.py +0 -0
  42. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/__init__.py +0 -0
  43. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/datalayer.py +0 -0
  44. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/daytona.py +0 -0
  45. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/e2b.py +0 -0
  46. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/adapters/managed.py +0 -0
  47. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/attest.py +0 -0
  48. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/bases.py +0 -0
  49. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/breaker.py +0 -0
  50. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/build_secrets.py +0 -0
  51. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/builders.py +0 -0
  52. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/canonical.py +0 -0
  53. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/conformance.py +0 -0
  54. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  55. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  56. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  57. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/contract.py +0 -0
  58. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/__init__.py +0 -0
  59. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/build.py +0 -0
  60. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  61. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  62. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/errors.py +0 -0
  63. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/files.py +0 -0
  64. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/image_import.py +0 -0
  65. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/lifecycle.py +0 -0
  66. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/policy.py +0 -0
  67. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/redact.py +0 -0
  68. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/resolve.py +0 -0
  69. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/resolve_conda.py +0 -0
  70. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/schema.py +0 -0
  71. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/environments/spec.py +0 -0
  72. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/eval_sandbox.py +0 -0
  73. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/exceptions.py +0 -0
  74. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/filesystem.py +0 -0
  75. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/google_colab.py +0 -0
  76. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/google_colab_sandbox.py +0 -0
  77. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/interfaces.py +0 -0
  78. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/jupyter_ingress.py +0 -0
  79. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  80. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle.py +0 -0
  81. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_execute.py +0 -0
  82. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_live.py +0 -0
  83. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/kaggle_sandbox.py +0 -0
  84. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/lifecycle.py +0 -0
  85. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/manage.py +0 -0
  86. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/modal_sandbox.py +0 -0
  87. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/models.py +0 -0
  88. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/monty_sandbox.py +0 -0
  89. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/provider_ingress.py +0 -0
  90. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/code_sandboxes/providers.py +0 -0
  91. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docker/Dockerfile +0 -0
  92. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/.gitignore +0 -0
  93. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/LICENSE +0 -0
  94. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/Makefile +0 -0
  95. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/README.md +0 -0
  96. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/babel.config.js +0 -0
  97. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/api-reference/_category_.yml +0 -0
  98. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/api-reference/index.mdx +0 -0
  99. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/cli/_category_.yml +0 -0
  100. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/cli/index.mdx +0 -0
  101. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/contribute/_category_.yml +0 -0
  102. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/contribute/index.mdx +0 -0
  103. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/_category_.yml +0 -0
  104. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/contract.mdx +0 -0
  105. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/index.mdx +0 -0
  106. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/specification.mdx +0 -0
  107. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/environments/spike-2026-09.mdx +0 -0
  108. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/examples/_category_.yml +0 -0
  109. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/examples/index.mdx +0 -0
  110. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/guide/_category_.yml +0 -0
  111. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/guide/index.mdx +0 -0
  112. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/index.mdx +0 -0
  113. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/install/_category_.yml +0 -0
  114. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/install/index.mdx +0 -0
  115. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/provider-ingress.mdx +0 -0
  116. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/_category_.yml +0 -0
  117. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/cloudflare.mdx +0 -0
  118. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/coreweave.mdx +0 -0
  119. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/datalayer.mdx +0 -0
  120. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/daytona.mdx +0 -0
  121. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/docker.mdx +0 -0
  122. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/e2b.mdx +0 -0
  123. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/eval.mdx +0 -0
  124. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/google-colab.mdx +0 -0
  125. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/index.mdx +0 -0
  126. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/jupyter-server.mdx +0 -0
  127. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/kaggle.mdx +0 -0
  128. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/modal.mdx +0 -0
  129. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docs/providers/monty.mdx +0 -0
  130. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/docusaurus.config.js +0 -0
  131. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/package.json +0 -0
  132. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/sidebars.js +0 -0
  133. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow1.js +0 -0
  134. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow1.module.css +0 -0
  135. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow2.js +0 -0
  136. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow2.module.css +0 -0
  137. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow3.js +0 -0
  138. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/components/HomepageRow3.module.css +0 -0
  139. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/css/custom.css +0 -0
  140. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/pages/index.module.css +0 -0
  141. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/pages/markdown-page.md +0 -0
  142. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/theme/CustomDocItem.tsx +0 -0
  143. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/src/theme/IconsTitle.tsx +0 -0
  144. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/bricks.svg +0 -0
  145. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/building-construction.svg +0 -0
  146. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/cloud-sun.svg +0 -0
  147. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/cloud.svg +0 -0
  148. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/collaboration.svg +0 -0
  149. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/copy-left.svg +0 -0
  150. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/logo.png +0 -0
  151. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/logo.svg +0 -0
  152. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/business.svg +0 -0
  153. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  154. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/personas/devops.svg +0 -0
  155. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/datalayer/project/project-line.svg +0 -0
  156. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/favicon.ico +0 -0
  157. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/jupyter.svg +0 -0
  158. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/open-source.svg +0 -0
  159. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/react-js.svg +0 -0
  160. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/react-js2.svg +0 -0
  161. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/ringed-planet.svg +0 -0
  162. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/robot.svg +0 -0
  163. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/rocket.svg +0 -0
  164. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/shield.svg +0 -0
  165. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/sparkles.svg +0 -0
  166. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/streamlit.gif +0 -0
  167. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/super-hero.svg +0 -0
  168. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/target.svg +0 -0
  169. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/docs/static/img/thumbs-up.svg +0 -0
  170. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/README.md +0 -0
  171. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/Makefile +0 -0
  172. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/cloudflare_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/coreweave_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/datalayer_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/daytona_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/docker_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/e2b_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/eval_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/google_colab_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/kaggle_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/modal_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/exec/monty_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/Makefile +0 -0
  185. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/cloudflare_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/coreweave_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/datalayer_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/daytona_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/docker_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/e2b_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/eval_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/google_colab_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/kaggle_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/modal_sandbox_example.py +0 -0
  196. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/examples/repl/monty_sandbox_example.py +0 -0
  197. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/schemas/environment-v1alpha1.json +0 -0
  198. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/__init__.py +0 -0
  199. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/conftest.py +0 -0
  200. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  201. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_bridge_mount.py +0 -0
  202. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_builds.py +0 -0
  203. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cli_exec.py +0 -0
  204. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cli_repl.py +0 -0
  205. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client.py +0 -0
  206. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_contents.py +0 -0
  207. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_files.py +0 -0
  208. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_client_outcome_error_output.py +0 -0
  209. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_cloudflare.py +0 -0
  210. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_commands_envelope.py +0 -0
  211. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_console.py +0 -0
  212. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_contents_contract.py +0 -0
  213. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_contents_live_matrix.py +0 -0
  214. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_coreweave.py +0 -0
  215. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_datalayer_sandbox.py +0 -0
  216. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_daytona.py +0 -0
  217. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_e2b.py +0 -0
  218. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_accounts.py +0 -0
  219. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_attest.py +0 -0
  220. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_bases.py +0 -0
  221. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_breaker.py +0 -0
  222. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_build_secrets.py +0 -0
  223. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_canonical.py +0 -0
  224. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_conformance.py +0 -0
  225. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_constraints.py +0 -0
  226. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_cross_variant_report.py +0 -0
  227. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_datalayer_builder.py +0 -0
  228. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_daytona_builder.py +0 -0
  229. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_dockerfile_source.py +0 -0
  230. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_doctor.py +0 -0
  231. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_e2b_builder.py +0 -0
  232. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_errors.py +0 -0
  233. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_image_import.py +0 -0
  234. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_lifecycle.py +0 -0
  235. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_managed_builders.py +0 -0
  236. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_organization_policy.py +0 -0
  237. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_redact.py +0 -0
  238. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_resolve.py +0 -0
  239. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_resolve_conda.py +0 -0
  240. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_spec.py +0 -0
  241. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_environment_spec_organization_policy.py +0 -0
  242. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_eval.py +0 -0
  243. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  244. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_factory.py +0 -0
  245. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_google_colab.py +0 -0
  246. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_integration.py +0 -0
  247. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_ingress.py +0 -0
  248. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_server.py +0 -0
  249. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_jupyter_server_streaming.py +0 -0
  250. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle.py +0 -0
  251. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_execute.py +0 -0
  252. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_live.py +0 -0
  253. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kaggle_session.py +0 -0
  254. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_kernel_client_compatibility.py +0 -0
  255. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_lifecycle_vocabulary.py +0 -0
  256. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_manage.py +0 -0
  257. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_manage_datalayer.py +0 -0
  258. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_modal_google_colab_sandbox.py +0 -0
  259. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_modal_session.py +0 -0
  260. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_models.py +0 -0
  261. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_output_line_fidelity.py +0 -0
  262. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_provider_catalog_names.py +0 -0
  263. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_provider_ingress.py +0 -0
  264. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_providers.py +0 -0
  265. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_sandboxes.py +0 -0
  266. {code_sandboxes-1.9.34 → code_sandboxes-1.9.35}/tests/test_the_kill_relaunch_drill.py +0 -0
@@ -4,18 +4,19 @@ name: Environments Live Matrix
4
4
  # providers, nightly (PLAN_ENV.md E2-11): a real, hash-verified lock, a real
5
5
  # build, a real launch, and the formal core tier — tests/test_environment_live_matrix.py.
6
6
  #
7
- # E2B and Modal carry their own already-documented, unticked gaps there
8
- # (E2-03's uid/gid mismatch; modal_sandbox.py's missing setpriv) and are
9
- # `xfail(strict=False)` in that file, so an ordinary failure there is
10
- # expected and does not fail this workflow. Daytona carries no such marker:
11
- # a failure there is a real regression, and this workflow opens (or
12
- # comments on) an issue naming it.
7
+ # E2B carries its own documented, unticked gap there (E2-03: its services run
8
+ # as root) and is `xfail(strict=False)` in that file, so an ordinary failure
9
+ # there is expected and does not fail this workflow. Daytona and Modal carry
10
+ # no such marker: a failure there is a real regression, and this workflow
11
+ # opens (or comments on) an issue naming it.
13
12
  #
14
- # Needs, as repository secrets: E2B_API_KEY, DAYTONA_API_KEY, MODAL_TOKEN_ID,
15
- # MODAL_TOKEN_SECRET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY — the last two
16
- # scoped to read the approved base from ECR only (D-18). A secret that is
17
- # not set is not a workflow failure: the test each secret belongs to skips,
18
- # by name, and says so in the job summary.
13
+ # Needs one repository secret, DATALAYER_API_KEY. The providers' keys are
14
+ # Datalayer secrets of the account it belongs to, read at run time: E2B_API_KEY,
15
+ # DAYTONA_API_KEY, MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, and the AWS pair under
16
+ # CODE_SANDBOXES_CI_AWS_ACCESS_KEY_ID and CODE_SANDBOXES_CI_AWS_SECRET_ACCESS_KEY,
17
+ # a key of the base reader that can only pull the approved base from ECR (D-18).
18
+ # A key that is not there skips the test it belongs to, by name, and the job
19
+ # summary lists each leg's outcome; a Daytona leg that did not run fails the job.
19
20
 
20
21
  on:
21
22
  schedule:
@@ -55,6 +56,48 @@ jobs:
55
56
  uv pip install ".[e2b,daytona,modal,test]"
56
57
  uv pip install boto3
57
58
 
59
+ # Each value is masked before it reaches the job's environment, and none
60
+ # is set on a later step's own `env:`, which would override it with an
61
+ # empty string.
62
+ - name: Read the providers' keys from Datalayer
63
+ env:
64
+ DATALAYER_API_KEY: ${{ secrets.DATALAYER_API_KEY }}
65
+ DATALAYER_IAM_URL: https://prod1.datalayer.run
66
+ run: |
67
+ python - <<'PY'
68
+ import json
69
+ import os
70
+ import urllib.request
71
+
72
+ key = os.environ.get("DATALAYER_API_KEY", "")
73
+ if not key:
74
+ print("DATALAYER_API_KEY is not a secret of this repository: every leg will skip")
75
+ raise SystemExit(0)
76
+ request = urllib.request.Request(
77
+ os.environ["DATALAYER_IAM_URL"] + "/api/iam/v1/secrets/values",
78
+ headers={"Authorization": f"Bearer {key}"},
79
+ )
80
+ with urllib.request.urlopen(request, timeout=60) as response:
81
+ secrets = json.load(response).get("secrets") or {}
82
+ wanted = {
83
+ "DAYTONA_API_KEY": "DAYTONA_API_KEY",
84
+ "E2B_API_KEY": "E2B_API_KEY",
85
+ "MODAL_TOKEN_ID": "MODAL_TOKEN_ID",
86
+ "MODAL_TOKEN_SECRET": "MODAL_TOKEN_SECRET",
87
+ "AWS_ACCESS_KEY_ID": "CODE_SANDBOXES_CI_AWS_ACCESS_KEY_ID",
88
+ "AWS_SECRET_ACCESS_KEY": "CODE_SANDBOXES_CI_AWS_SECRET_ACCESS_KEY",
89
+ }
90
+ with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as environment:
91
+ for variable, name in wanted.items():
92
+ value = str(secrets.get(name) or "")
93
+ if not value or "\n" in value:
94
+ print(f"{variable}: no Datalayer secret {name} for this account")
95
+ continue
96
+ print(f"::add-mask::{value}")
97
+ environment.write(f"{variable}={value}\n")
98
+ print(f"{variable}: read from the Datalayer secret {name}")
99
+ PY
100
+
58
101
  - name: Configure AWS credentials
59
102
  run: |
60
103
  mkdir -p ~/.aws
@@ -63,9 +106,6 @@ jobs:
63
106
  echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
64
107
  echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY}"
65
108
  } > ~/.aws/credentials
66
- env:
67
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
68
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
69
109
 
70
110
  - name: Run the live matrix
71
111
  id: live
@@ -75,13 +115,51 @@ jobs:
75
115
  tests/test_environment_live_matrix.py \
76
116
  --junitxml=live-matrix-results.xml
77
117
  env:
78
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
79
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
80
118
  AWS_REGION: us-east-1
81
- E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
82
- DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
83
- MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
84
- MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
119
+
120
+ # A leg whose secret is not set skips, and a run where every leg skipped
121
+ # reported success in under a second: the nightly was green every night
122
+ # while it tested nothing (found on 2026-09-18, "3 skipped in 0.61s").
123
+ # Each leg's outcome goes in the job summary, and a Daytona leg that did
124
+ # not run fails the job: it is the one this matrix is required to prove
125
+ # (PLAN_ENV.md, the Datalayer and Daytona target, E2-11).
126
+ - name: Say which legs ran, and refuse a Daytona leg that did not
127
+ if: always()
128
+ run: |
129
+ python - <<'PY'
130
+ import os
131
+ import sys
132
+ import xml.etree.ElementTree as ET
133
+
134
+ try:
135
+ cases = list(ET.parse("live-matrix-results.xml").getroot().iter("testcase"))
136
+ except (OSError, ET.ParseError) as error:
137
+ sys.exit(f"no JUnit report to read: {error}")
138
+ rows, daytona = [], None
139
+ for case in cases:
140
+ leg = (case.get("classname") or "").rsplit(".", 1)[-1]
141
+ skipped = case.find("skipped")
142
+ if skipped is not None:
143
+ outcome = "skipped: " + (skipped.get("message") or "").strip()
144
+ elif case.find("failure") is not None or case.find("error") is not None:
145
+ outcome = "failed"
146
+ else:
147
+ outcome = "passed"
148
+ rows.append(f"| {leg} | {outcome} |")
149
+ if leg == "TestDaytona":
150
+ daytona = outcome
151
+ summary = "| Leg | Outcome |\n|---|---|\n" + "\n".join(rows) + "\n"
152
+ with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as handle:
153
+ handle.write(summary)
154
+ print(summary)
155
+ if daytona is None or daytona.startswith("skipped"):
156
+ sys.exit(
157
+ f"The Daytona leg did not run ({daytona or 'absent'}): it is the one this "
158
+ "matrix must prove, so a run without it is not a green run. Set the "
159
+ "DATALAYER_API_KEY repository secret, whose account holds the "
160
+ "DAYTONA_API_KEY and CODE_SANDBOXES_CI_AWS_* Datalayer secrets."
161
+ )
162
+ PY
85
163
 
86
164
  - name: Upload the JUnit report
87
165
  if: always()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.9.34
3
+ Version: 1.9.35
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.34"
6
+ __version__ = "1.9.35"
@@ -96,13 +96,11 @@ end and the container exited before that first real exec ever reached it.
96
96
  The entrypoint now execs `sleep infinity` when it is given no arguments,
97
97
  and `"$@"` when it is — correct either way a caller invokes it.
98
98
 
99
- **A GPU size class is left buildable at `validate()`,** matching an existing
100
- test (`test_a_gpu_spec_is_buildable_on_modal_and_daytona`), and refused at
101
- `build()` instead, naming E2-17 — the same reasoning as Daytona's: the CUDA
102
- base is not published yet, so this cannot be reached in practice, and this
103
- builder does not know the launch-time `gpu=` argument to hand a size class
104
- either (D-20 leaves that to a later item; a GPU is Modal's own launch
105
- option, not an image property, per section 11.4 item 10).
99
+ **A GPU is a launch option on Modal, not part of the image** (section 11.4
100
+ item 10, E2-17). A GPU version builds the same image any version does, on the
101
+ CUDA base; the spec's `accelerator` names one of Modal's GPUs, checked at
102
+ `validate`, and the smoke test launches the image on it (`gpu="T4"`,
103
+ `"H100:2"`) and adds check 11 to the core tier.
106
104
 
107
105
  **A build secret is attached to the `postInstall` steps that name it (E3-05).**
108
106
  `run_commands` takes a per-step `secrets=` collection, a mechanism E2B and
@@ -149,14 +147,42 @@ from ..files import files_step
149
147
  from ..redact import redact
150
148
  from ..resolve import WHEELHOUSE_IMAGE_PATH, apt_pins_in
151
149
  from ..resolve_conda import conda_lock_pip_requirements, is_conda_lock
152
- from ..spec import GPU_SIZE_CLASSES, BuildSecret, Environment, command_names_secret
150
+ from ..spec import BuildSecret, Environment, command_names_secret
153
151
  from .managed import ManagedBuilder
154
152
 
155
- __all__ = ["UNIMPLEMENTED_INSTRUCTIONS", "Builder"]
153
+ __all__ = ["MODAL_GPUS", "UNIMPLEMENTED_INSTRUCTIONS", "Builder", "modal_gpu"]
156
154
 
157
155
  #: What Modal's own Dockerfile builder does not implement (§6).
158
156
  UNIMPLEMENTED_INSTRUCTIONS = ("ONBUILD", "STOPSIGNAL", "VOLUME")
159
157
 
158
+ #: The GPUs a Modal sandbox takes by name (E2-17), as its `gpu=` spells them.
159
+ MODAL_GPUS: tuple[str, ...] = (
160
+ "T4",
161
+ "L4",
162
+ "A10G",
163
+ "L40S",
164
+ "A100",
165
+ "A100-40GB",
166
+ "A100-80GB",
167
+ "H100",
168
+ "H200",
169
+ "B200",
170
+ )
171
+
172
+
173
+ def modal_gpu(accelerator_type: str, count: int = 1) -> str | None:
174
+ """Modal's `gpu=` for an accelerator, or `None` when Modal has no such GPU.
175
+
176
+ `t4` and `a100_80gb` name what Modal calls `T4` and `A100-80GB`, and a
177
+ count above one is Modal's `"H100:2"`. An `RTX-4090` is Daytona's
178
+ vocabulary, not Modal's.
179
+ """
180
+ name = accelerator_type.strip().upper().replace("_", "-")
181
+ if name not in MODAL_GPUS:
182
+ return None
183
+ return f"{name}:{count}" if count > 1 else name
184
+
185
+
160
186
  #: `uv`, pinned the same way every other builder's bootstrap is (E1-04/E3-04).
161
187
  _UV_VERSION = "0.12.11"
162
188
 
@@ -269,7 +295,10 @@ def _intermediates_of(built: Any) -> tuple[str, ...]:
269
295
  if hasattr(dependency, "deps"):
270
296
  walk(dependency)
271
297
  object_id = getattr(image, "object_id", None)
272
- if object_id and image is not built:
298
+ # Only images: the build's ECR secret is in `deps()` too, and has an
299
+ # `st-` id `ImageDelete` refuses ("not a valid Image ID", found live
300
+ # on 2026-09-18). It is deleted by the build itself, as a secret.
301
+ if object_id and image is not built and str(object_id).startswith("im-"):
273
302
  found.append(str(object_id))
274
303
 
275
304
  try:
@@ -393,6 +422,18 @@ class Builder(ManagedBuilder):
393
422
  field=f"spec.buildSecrets[{index}].mountAs",
394
423
  )
395
424
  )
425
+ accelerator = environment.spec.resources.accelerator
426
+ if accelerator != "none" and modal_gpu(accelerator.type) is None:
427
+ findings.append(
428
+ CapabilityFinding(
429
+ code="DL_ENV_CAPABILITY_UNSUPPORTED",
430
+ message=(
431
+ f"Modal has no GPU called `{accelerator.type}`; it offers "
432
+ + ", ".join(MODAL_GPUS)
433
+ ),
434
+ field="spec.resources.accelerator.type",
435
+ )
436
+ )
396
437
  return findings
397
438
 
398
439
  # -- Building -------------------------------------------------------------
@@ -406,18 +447,6 @@ class Builder(ManagedBuilder):
406
447
  launch, not here, for the same reason.
407
448
  """
408
449
  spec = request.environment.spec
409
- if request.size_class in GPU_SIZE_CLASSES:
410
- # `validate` leaves a GPU class buildable (`gpu = True`, D-20):
411
- # the CUDA base E2-17 has not published yet, so this cannot be
412
- # reached in practice — refused plainly here rather than
413
- # guessing at the launch-time `gpu=` argument this class needs
414
- # (section 11.4 item 10 is a launch concern, not a build one).
415
- raise EnvironmentsError(
416
- CAPABILITY_UNSUPPORTED,
417
- f"Modal runs `{request.size_class}` on its own GPUs, but the CUDA base this "
418
- "needs is E2-17's, not built yet",
419
- detail={"variant": self.variant, "missing": "E2-17"},
420
- )
421
450
  # Resolved before Modal is touched: a secret IAM will not give stops
422
451
  # the build with nothing to clean up in the owner's workspace.
423
452
  declared, values = self._resolved_secrets(request)
@@ -717,11 +746,13 @@ class Builder(ManagedBuilder):
717
746
  )
718
747
  from ...modal_sandbox import ModalSandbox
719
748
  from ...models import SandboxConfig
720
- from ..conformance import expected_packages, run_core_tier
749
+ from ..conformance import expected_packages, run_accelerator_check, run_core_tier
721
750
 
722
751
  sdk = self._modal_sdk()
752
+ accelerator = environment.spec.resources.accelerator
753
+ gpu = None if accelerator == "none" else modal_gpu(accelerator.type, accelerator.count)
723
754
  sandbox = ModalSandbox(
724
- config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}"),
755
+ config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}", gpu=gpu),
725
756
  app_name=f"dl-{environment.metadata.name}",
726
757
  image_id=artifact.provider_artifact_id,
727
758
  client=self._client(sdk),
@@ -729,13 +760,21 @@ class Builder(ManagedBuilder):
729
760
  self._log(f"Launching {artifact.provider_artifact_id} to smoke-test it")
730
761
  try:
731
762
  sandbox.start()
732
- return run_core_tier(
763
+ result = run_core_tier(
733
764
  sandbox,
734
765
  python_version=environment.spec.language.version,
735
766
  expected_packages=expected_packages(environment, lock_text or ""),
736
767
  secret_values=tuple(secret_values),
737
768
  restart=lambda: self._restart(sandbox),
738
769
  )
770
+ if gpu is not None:
771
+ # The core tier passes on a machine with no GPU: a GPU version
772
+ # is the version its spec describes only when its GPUs are
773
+ # visible and its CUDA is the spec's (check 11, E2-17).
774
+ result.checks.append(
775
+ run_accelerator_check(sandbox, cuda=accelerator.cuda, count=accelerator.count)
776
+ )
777
+ return result
739
778
  except EnvironmentsError:
740
779
  raise
741
780
  except Exception as error:
@@ -105,6 +105,10 @@ filterwarnings = [
105
105
  # use of the client in a test became an exception. It took the live matrix
106
106
  # against a real Datalayer sandbox down before the sandbox was reached.
107
107
  "module:Datalayer is migrating its paths to use standard platformdirs:DeprecationWarning",
108
+ # The Daytona SDK (0.205) reads its own deprecated `DaytonaConfig.server_url`
109
+ # inside `Daytona(config)`: with `error` in force, every live Daytona test
110
+ # failed before reaching Daytona, and a nightly that skipped hid it.
111
+ "ignore:`server_url` is deprecated:DeprecationWarning",
108
112
  ]
109
113
 
110
114
  [tool.mypy]
@@ -326,10 +326,10 @@ def test_files_and_contents_build_are_baked_together() -> None:
326
326
 
327
327
 
328
328
  def test_a_contents_build_entry_without_its_digest_will_not_parse() -> None:
329
- with pytest.raises(Exception):
330
- environment(
331
- contents_build=[{"source": "https://data.example/x", "path": "/opt/x"}]
332
- )
329
+ with pytest.raises(EnvironmentsError) as raised:
330
+ environment(contents_build=[{"source": "https://data.example/x", "path": "/opt/x"}])
331
+ assert raised.value.code.code == "DL_ENV_SPEC_INVALID"
332
+ assert "sha256" in raised.value.message
333
333
 
334
334
 
335
335
  def test_the_neutral_modules_import_no_provider_sdk() -> None:
@@ -163,9 +163,7 @@ def test_a_plain_build_context_passes() -> None:
163
163
  ),
164
164
  ],
165
165
  )
166
- def test_a_forbidden_context_member_is_refused(
167
- entry: BuildContextEntry, message: str
168
- ) -> None:
166
+ def test_a_forbidden_context_member_is_refused(entry: BuildContextEntry, message: str) -> None:
169
167
  findings = validate_build_context([entry])
170
168
  assert any(finding.message == message for finding in findings), findings
171
169
  with pytest.raises(EnvironmentsError) as refused:
@@ -243,25 +243,12 @@ class TestDaytona:
243
243
 
244
244
 
245
245
  class TestModal:
246
- """`xfail(strict=False)`: checks 5 (imports) and 6 (filesystem) fail —
247
- confirmed live, 2026-09-13, the session driver refusing to start
248
- against a sandbox that reports itself already shutting down, while 1,
249
- 2, 3, 4, 7, 8 and 9 all pass. This used to be attributed to the
250
- identity gap (checks 1/2 failed too, then) — closed since, live: a
251
- contract-built artifact's `ModalSandbox` now drops its driver to
252
- `1000:100` (`_start_driver`, gated on `image_id` so a plain
253
- `ModalSandbox` is unaffected), and checks 1/2 pass with it. 5/6 turned
254
- out to be a separate, still-unexplained issue: a plain, non-Environments
255
- `ModalSandbox` runs several sequential snippets with no trouble at all
256
- over the same span, so this is specific to a contract-built artifact's
257
- image under repeated `exec`. An `XPASS` here means that's closed too."""
246
+ """No `xfail` since 2026-09-18: a real artifact, built by the production
247
+ builder and launched by image id, passes all nine checks, check 9 included
248
+ with a secret to scan for, right after check 8. The break recorded under
249
+ E2-05 (the exec channel gone after check 8) no longer reproduces; the
250
+ kernel-restart fix of 1.9.28, check 7's own path, is the likeliest cause."""
258
251
 
259
- @pytest.mark.xfail(
260
- reason="checks 5 and 6 fail on a session driver restart the sandbox refuses, "
261
- "specific to a contract-built artifact and not yet root-caused (identity, "
262
- "checks 1 and 2, is fixed)",
263
- strict=False,
264
- )
265
252
  def test_build_launch_and_the_core_tier(self, real_lock: tuple[str, str]) -> None:
266
253
  _skip_unless_available("modal")
267
254
  import modal
@@ -23,7 +23,7 @@ from typing import Any, ClassVar
23
23
 
24
24
  import pytest
25
25
 
26
- from code_sandboxes.environments.adapters.modal import Builder
26
+ from code_sandboxes.environments.adapters.modal import MODAL_GPUS, Builder, modal_gpu
27
27
  from code_sandboxes.environments.builders import ArtifactReference, BuildRequest
28
28
  from code_sandboxes.environments.errors import (
29
29
  ARTIFACT_MISSING,
@@ -748,16 +748,84 @@ class TestTheEcrSecretIsCleanedUp:
748
748
  assert deleted.kwargs["secret_id"] == secret.object_id
749
749
 
750
750
 
751
- class TestWhatModalCannotBuildYet:
752
- def test_a_gpu_size_class_is_refused_at_build_time_naming_e2_17(self) -> None:
751
+ class TestAGpuVersion:
752
+ """E2-17: on Modal a GPU is a launch option, not part of the image."""
753
+
754
+ GPU: ClassVar[dict[str, Any]] = {
755
+ "base": {"ref": "datalayer/python-cuda", "channel": "2026.09"},
756
+ "resources": {
757
+ "sizeClass": "gpu-small",
758
+ "accelerator": {"type": "l4", "count": 2, "cuda": "12.8"},
759
+ },
760
+ }
761
+
762
+ def test_it_builds_the_image_any_version_builds(self) -> None:
753
763
  modal = FakeModalModule()
754
- with pytest.raises(EnvironmentsError) as raised:
755
- a_builder(modal=modal).build(a_request(size_class="gpu-large"))
756
- assert raised.value.code.code == CAPABILITY_UNSUPPORTED.code
757
- assert raised.value.detail["missing"] == "E2-17"
758
- # Refused before any provider is touched.
759
- assert modal.Secret.from_dict_calls == []
760
- assert modal.Image.from_aws_ecr_calls == []
764
+ artifact = a_builder(modal=modal).build(a_request(spec=self.GPU, size_class="gpu-small"))
765
+ assert artifact.provider_artifact_id.startswith("im-")
766
+ assert modal.Image.from_aws_ecr_calls, "built from the CUDA base like any image"
767
+
768
+ def test_a_gpu_modal_does_not_offer_is_refused_before_any_build(self) -> None:
769
+ spec = {
770
+ **self.GPU,
771
+ "resources": {"sizeClass": "gpu-small", "accelerator": {"type": "RTX-4090"}},
772
+ }
773
+ report = a_builder().validate(a_request(spec=spec).environment)
774
+ assert report.supported is False
775
+ [finding] = [item for item in report.findings if "RTX-4090" in item.message]
776
+ assert finding.field == "spec.resources.accelerator.type"
777
+ assert all(name in finding.message for name in MODAL_GPUS)
778
+
779
+ def test_a_name_is_modal_s_gpu_argument(self) -> None:
780
+ assert [
781
+ modal_gpu("t4"),
782
+ modal_gpu("a100_80gb"),
783
+ modal_gpu("H100", 2),
784
+ modal_gpu("RTX-4090"),
785
+ ] == [
786
+ "T4",
787
+ "A100-80GB",
788
+ "H100:2",
789
+ None,
790
+ ]
791
+
792
+ def test_the_smoke_test_runs_on_the_gpu_and_adds_check_eleven(self, monkeypatch) -> None:
793
+ from code_sandboxes.environments.builders import CheckResult, ValidationResult
794
+
795
+ made: dict = {}
796
+ asked: dict = {}
797
+
798
+ class FakeSandbox:
799
+ def __init__(self, **kwargs):
800
+ made.update(kwargs)
801
+
802
+ def start(self):
803
+ pass
804
+
805
+ def stop(self):
806
+ pass
807
+
808
+ monkeypatch.setattr("code_sandboxes.modal_sandbox.ModalSandbox", FakeSandbox)
809
+ monkeypatch.setattr(
810
+ "code_sandboxes.environments.conformance.run_core_tier",
811
+ lambda sandbox, **kwargs: ValidationResult(contract_version="sandbox-contract/v1"),
812
+ )
813
+ monkeypatch.setattr(
814
+ "code_sandboxes.environments.conformance.run_accelerator_check",
815
+ lambda sandbox, **kwargs: asked.update(kwargs)
816
+ or CheckResult(id="conformance:11", name="gpu", passed=True, gating=True),
817
+ )
818
+ environment = a_request(spec=self.GPU, size_class="gpu-small").environment
819
+ artifact = ArtifactReference(
820
+ variant="modal",
821
+ immutable_reference="im-1",
822
+ provider_artifact_id="im-1",
823
+ contract_version="sandbox-contract/v1",
824
+ )
825
+ answer = a_builder().smoke_test(artifact, environment=environment, lock_text=LOCK)
826
+ assert made["config"].gpu == "L4:2"
827
+ assert asked == {"cuda": "12.8", "count": 2}
828
+ assert [check.id for check in answer.checks] == ["conformance:11"]
761
829
 
762
830
 
763
831
  class TestABuildSecret:
@@ -958,6 +1026,16 @@ class TestTheIntermediateLayers:
958
1026
  # The built image is the artifact, not an intermediate.
959
1027
  assert _intermediates_of(built) == ("im-base", "im-middle")
960
1028
 
1029
+ def test_the_builds_ecr_secret_is_not_an_intermediate(self) -> None:
1030
+ """It sits in `deps()` beside the layers, with an `st-` id `ImageDelete`
1031
+ refuses as "not a valid Image ID" (found live, 2026-09-18)."""
1032
+ from code_sandboxes.environments.adapters.modal import _intermediates_of
1033
+
1034
+ secret = _Layer(object_id="st-ecr", deps=lambda: ())
1035
+ base = _Layer(object_id="im-base", deps=lambda: (secret,))
1036
+ built = _Layer(object_id="im-built", deps=lambda: (base,))
1037
+ assert _intermediates_of(built) == ("im-base",)
1038
+
961
1039
  def test_a_layer_with_no_id_is_not_recorded(self) -> None:
962
1040
  """Only a hydrated layer has an id worth writing down."""
963
1041
  from code_sandboxes.environments.adapters.modal import _intermediates_of
File without changes