code-sandboxes 1.8.0__tar.gz → 1.9.1__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.8.0 → code_sandboxes-1.9.1}/.github/workflows/build.yml +1 -1
  2. code_sandboxes-1.9.1/.github/workflows/environments-live.yml +181 -0
  3. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-tests.yml +1 -1
  4. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.pre-commit-config.yaml +5 -0
  5. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/CHANGELOG.md +100 -4
  6. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/Makefile +5 -0
  7. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/PKG-INFO +4 -2
  8. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/README.md +1 -1
  9. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/__version__.py +1 -1
  10. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/base.py +13 -2
  11. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/client.py +8 -1
  12. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/commands.py +1 -1
  13. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/datalayer_sandbox.py +10 -10
  14. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/daytona_sandbox.py +14 -10
  15. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/docker_sandbox.py +2 -3
  16. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/e2b_sandbox.py +21 -9
  17. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/__init__.py +5 -0
  18. code_sandboxes-1.9.1/code_sandboxes/environments/accounts.py +143 -0
  19. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/adapters/datalayer.py +232 -67
  20. code_sandboxes-1.9.1/code_sandboxes/environments/adapters/daytona.py +532 -0
  21. code_sandboxes-1.9.1/code_sandboxes/environments/adapters/e2b.py +533 -0
  22. code_sandboxes-1.9.1/code_sandboxes/environments/adapters/managed.py +242 -0
  23. code_sandboxes-1.9.1/code_sandboxes/environments/adapters/modal.py +565 -0
  24. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/attest.py +18 -1
  25. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/bases.py +13 -2
  26. code_sandboxes-1.9.1/code_sandboxes/environments/breaker.py +294 -0
  27. code_sandboxes-1.9.1/code_sandboxes/environments/build_secrets.py +152 -0
  28. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/builders.py +15 -1
  29. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/conformance.py +94 -5
  30. code_sandboxes-1.9.1/code_sandboxes/environments/constraints/wheelhouse/README.md +40 -0
  31. code_sandboxes-1.9.1/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  32. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/datalayer_sandbox.py +3 -1
  33. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/errors.py +16 -0
  34. code_sandboxes-1.9.1/code_sandboxes/environments/image_import.py +289 -0
  35. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/resolve.py +357 -13
  36. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/spec.py +260 -25
  37. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/jupyter_ingress.py +1 -2
  38. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/jupyter_server_sandbox.py +29 -0
  39. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/modal_sandbox.py +150 -35
  40. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/index.mdx +3 -1
  41. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/specification.mdx +1 -1
  42. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/index.mdx +1 -1
  43. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/daytona.mdx +32 -0
  44. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/e2b.mdx +31 -0
  45. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/modal.mdx +30 -0
  46. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docusaurus.config.js +1 -1
  47. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/README.md +1 -1
  48. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/pyproject.toml +4 -2
  49. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/schemas/environment-v1alpha1.json +75 -0
  50. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +1 -0
  51. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_bridge_mount.py +30 -18
  52. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client.py +25 -0
  53. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_commands_envelope.py +5 -1
  54. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_contents_live_matrix.py +8 -9
  55. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_datalayer_sandbox.py +3 -1
  56. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_daytona.py +20 -0
  57. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_e2b.py +72 -1
  58. code_sandboxes-1.9.1/tests/test_environment_accounts.py +131 -0
  59. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_attest.py +28 -7
  60. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_bases.py +15 -4
  61. code_sandboxes-1.9.1/tests/test_environment_breaker.py +251 -0
  62. code_sandboxes-1.9.1/tests/test_environment_build_secrets.py +217 -0
  63. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_builders.py +40 -1
  64. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_conformance.py +31 -0
  65. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_contract.py +3 -3
  66. code_sandboxes-1.9.1/tests/test_environment_cross_variant_report.py +153 -0
  67. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_datalayer_builder.py +312 -18
  68. code_sandboxes-1.9.1/tests/test_environment_daytona_builder.py +786 -0
  69. code_sandboxes-1.9.1/tests/test_environment_e2b_builder.py +498 -0
  70. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_errors.py +2 -0
  71. code_sandboxes-1.9.1/tests/test_environment_image_import.py +202 -0
  72. code_sandboxes-1.9.1/tests/test_environment_live_matrix.py +312 -0
  73. code_sandboxes-1.9.1/tests/test_environment_managed_builders.py +384 -0
  74. code_sandboxes-1.9.1/tests/test_environment_modal_builder.py +746 -0
  75. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_redact.py +1 -3
  76. code_sandboxes-1.9.1/tests/test_environment_resolve.py +1131 -0
  77. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_spec.py +216 -1
  78. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_eval.py +10 -0
  79. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_server.py +149 -0
  80. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_server_streaming.py +14 -1
  81. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_modal_google_colab_sandbox.py +88 -0
  82. code_sandboxes-1.9.1/tests/test_modal_session.py +181 -0
  83. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_the_kill_relaunch_drill.py +9 -9
  84. code_sandboxes-1.8.0/tests/test_environment_resolve.py +0 -495
  85. code_sandboxes-1.8.0/tests/test_modal_session.py +0 -60
  86. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-code-style.yml +0 -0
  87. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/py-typing.yml +0 -0
  88. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.github/workflows/reusable-python.yml +0 -0
  89. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.gitignore +0 -0
  90. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.licenserc.yaml +0 -0
  91. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/.vscode/settings.json +0 -0
  92. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/Dockerfile +0 -0
  93. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/LICENSE +0 -0
  94. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/__init__.py +0 -0
  95. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/bridge_mount.py +0 -0
  96. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/builds.py +0 -0
  97. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/cli.py +0 -0
  98. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/cloudflare_sandbox.py +0 -0
  99. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/console.py +0 -0
  100. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/contents.py +0 -0
  101. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/coreweave_sandbox.py +0 -0
  102. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/__main__.py +0 -0
  103. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/adapters/__init__.py +0 -0
  104. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/canonical.py +0 -0
  105. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  106. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/contract.py +0 -0
  107. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/__init__.py +0 -0
  108. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/build.py +0 -0
  109. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  110. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/files.py +0 -0
  111. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/lifecycle.py +0 -0
  112. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/policy.py +0 -0
  113. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/redact.py +0 -0
  114. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/environments/schema.py +0 -0
  115. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/eval_sandbox.py +0 -0
  116. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/exceptions.py +0 -0
  117. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/filesystem.py +0 -0
  118. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/google_colab.py +0 -0
  119. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/google_colab_sandbox.py +1 -1
  120. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/interfaces.py +0 -0
  121. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle.py +0 -0
  122. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_execute.py +0 -0
  123. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_live.py +0 -0
  124. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/kaggle_sandbox.py +1 -1
  125. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/lifecycle.py +0 -0
  126. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/manage.py +0 -0
  127. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/models.py +0 -0
  128. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/monty_sandbox.py +0 -0
  129. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/provider_ingress.py +0 -0
  130. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/code_sandboxes/providers.py +0 -0
  131. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docker/Dockerfile +0 -0
  132. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/.gitignore +0 -0
  133. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/LICENSE +0 -0
  134. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/Makefile +0 -0
  135. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/README.md +0 -0
  136. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/babel.config.js +0 -0
  137. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/api-reference/_category_.yml +0 -0
  138. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/api-reference/index.mdx +0 -0
  139. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/cli/_category_.yml +0 -0
  140. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/cli/index.mdx +0 -0
  141. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/contribute/_category_.yml +0 -0
  142. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/contribute/index.mdx +0 -0
  143. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/_category_.yml +0 -0
  144. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/contract.mdx +0 -0
  145. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/environments/spike-2026-09.mdx +0 -0
  146. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/examples/_category_.yml +0 -0
  147. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/examples/index.mdx +0 -0
  148. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/guide/_category_.yml +0 -0
  149. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/guide/index.mdx +0 -0
  150. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/install/_category_.yml +0 -0
  151. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/install/index.mdx +0 -0
  152. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/provider-ingress.mdx +0 -0
  153. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/_category_.yml +0 -0
  154. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/cloudflare.mdx +0 -0
  155. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/coreweave.mdx +0 -0
  156. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/datalayer.mdx +0 -0
  157. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/docker.mdx +0 -0
  158. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/eval.mdx +0 -0
  159. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/google-colab.mdx +0 -0
  160. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/index.mdx +0 -0
  161. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/jupyter-server.mdx +0 -0
  162. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/kaggle.mdx +0 -0
  163. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/docs/providers/monty.mdx +0 -0
  164. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/package.json +0 -0
  165. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/sidebars.js +0 -0
  166. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow1.js +0 -0
  167. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow1.module.css +0 -0
  168. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow2.js +0 -0
  169. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow2.module.css +0 -0
  170. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow3.js +0 -0
  171. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/components/HomepageRow3.module.css +0 -0
  172. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/css/custom.css +0 -0
  173. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/pages/index.module.css +0 -0
  174. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/pages/markdown-page.md +0 -0
  175. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/theme/CustomDocItem.tsx +0 -0
  176. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/src/theme/IconsTitle.tsx +0 -0
  177. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/bricks.svg +0 -0
  178. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/building-construction.svg +0 -0
  179. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/cloud-sun.svg +0 -0
  180. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/cloud.svg +0 -0
  181. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/collaboration.svg +0 -0
  182. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/copy-left.svg +0 -0
  183. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/logo.png +0 -0
  184. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/logo.svg +0 -0
  185. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/business.svg +0 -0
  186. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  187. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/personas/devops.svg +0 -0
  188. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/datalayer/project/project-line.svg +0 -0
  189. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/favicon.ico +0 -0
  190. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/jupyter.svg +0 -0
  191. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/open-source.svg +0 -0
  192. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/react-js.svg +0 -0
  193. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/react-js2.svg +0 -0
  194. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/ringed-planet.svg +0 -0
  195. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/robot.svg +0 -0
  196. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/rocket.svg +0 -0
  197. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/shield.svg +0 -0
  198. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/sparkles.svg +0 -0
  199. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/streamlit.gif +0 -0
  200. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/super-hero.svg +0 -0
  201. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/target.svg +0 -0
  202. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/docs/static/img/thumbs-up.svg +0 -0
  203. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/Makefile +0 -0
  204. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/cloudflare_sandbox_example.py +0 -0
  205. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/coreweave_sandbox_example.py +0 -0
  206. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/datalayer_sandbox_example.py +0 -0
  207. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/daytona_sandbox_example.py +0 -0
  208. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/docker_sandbox_example.py +0 -0
  209. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/e2b_sandbox_example.py +0 -0
  210. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/eval_sandbox_example.py +0 -0
  211. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/google_colab_sandbox_example.py +0 -0
  212. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  213. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/kaggle_sandbox_example.py +0 -0
  214. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/modal_sandbox_example.py +0 -0
  215. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/exec/monty_sandbox_example.py +0 -0
  216. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/Makefile +0 -0
  217. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/cloudflare_sandbox_example.py +0 -0
  218. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/coreweave_sandbox_example.py +0 -0
  219. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/datalayer_sandbox_example.py +0 -0
  220. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/daytona_sandbox_example.py +0 -0
  221. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/docker_sandbox_example.py +0 -0
  222. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/e2b_sandbox_example.py +0 -0
  223. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/eval_sandbox_example.py +0 -0
  224. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/google_colab_sandbox_example.py +0 -0
  225. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  226. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/kaggle_sandbox_example.py +0 -0
  227. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/modal_sandbox_example.py +0 -0
  228. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/examples/repl/monty_sandbox_example.py +0 -0
  229. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/__init__.py +0 -0
  230. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/conftest.py +0 -0
  231. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_builds.py +0 -0
  232. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cli_exec.py +0 -0
  233. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cli_repl.py +0 -0
  234. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_contents.py +0 -0
  235. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_files.py +0 -0
  236. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_client_outcome_error_output.py +0 -0
  237. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_cloudflare.py +0 -0
  238. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_console.py +0 -0
  239. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_contents_contract.py +0 -0
  240. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_coreweave.py +0 -0
  241. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_canonical.py +0 -0
  242. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_constraints.py +0 -0
  243. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_doctor.py +0 -0
  244. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_environment_lifecycle.py +0 -0
  245. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  246. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_factory.py +0 -0
  247. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_google_colab.py +0 -0
  248. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_integration.py +0 -0
  249. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_jupyter_ingress.py +0 -0
  250. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle.py +0 -0
  251. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_execute.py +0 -0
  252. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_live.py +0 -0
  253. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kaggle_session.py +0 -0
  254. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_kernel_client_compatibility.py +0 -0
  255. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_lifecycle_vocabulary.py +0 -0
  256. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_manage.py +0 -0
  257. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_manage_datalayer.py +0 -0
  258. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_models.py +0 -0
  259. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_output_line_fidelity.py +0 -0
  260. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_provider_catalog_names.py +0 -0
  261. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_provider_ingress.py +0 -0
  262. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_providers.py +0 -0
  263. {code_sandboxes-1.8.0 → code_sandboxes-1.9.1}/tests/test_sandboxes.py +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  uses: ./.github/workflows/reusable-python.yml
17
17
  with:
18
18
  python-version: ${{ matrix.python-version }}
19
- install-extras: test,typing
19
+ install-extras: test,typing,datalayer
20
20
  extra-packages: dbus-python keyring secretstorage
21
21
  run-tests: true
22
22
  test-command: pytest tests/ --cov=code_sandboxes --cov-report term-missing
@@ -0,0 +1,181 @@
1
+ name: Environments Live Matrix
2
+
3
+ # The managed Environment builders (E2-03, E2-04, E2-05) against the real
4
+ # providers, nightly (PLAN_ENV.md E2-11): a real, hash-verified lock, a real
5
+ # build, a real launch, and the formal core tier — tests/test_environment_live_matrix.py.
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.
13
+ #
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.
19
+
20
+ on:
21
+ schedule:
22
+ # 04:17 UTC: off-peak for every provider this reaches, and away from the
23
+ # hour boundary every other scheduled workflow in this org tends to pick.
24
+ - cron: '17 4 * * *'
25
+ workflow_dispatch: {}
26
+
27
+ concurrency:
28
+ group: ${{ github.workflow }}
29
+ cancel-in-progress: false
30
+
31
+ # The default token on this repository is read-only: without this, a genuine
32
+ # matrix failure would still 403 trying to label and open the issue that is
33
+ # meant to report it (found in review).
34
+ permissions:
35
+ contents: read
36
+ issues: write
37
+
38
+ jobs:
39
+ live-matrix:
40
+ runs-on: ubuntu-latest
41
+ timeout-minutes: 45
42
+ steps:
43
+ - name: Checkout
44
+ uses: actions/checkout@v6
45
+
46
+ - name: Setup UV
47
+ uses: astral-sh/setup-uv@v7
48
+ with:
49
+ version: latest
50
+ python-version: '3.13'
51
+ activate-environment: true
52
+
53
+ - name: Install dependencies
54
+ run: |
55
+ uv pip install ".[e2b,daytona,modal,test]"
56
+ uv pip install boto3
57
+
58
+ - name: Configure AWS credentials
59
+ run: |
60
+ mkdir -p ~/.aws
61
+ {
62
+ echo "[default]"
63
+ echo "aws_access_key_id=${AWS_ACCESS_KEY_ID}"
64
+ echo "aws_secret_access_key=${AWS_SECRET_ACCESS_KEY}"
65
+ } > ~/.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
+
70
+ - name: Run the live matrix
71
+ id: live
72
+ continue-on-error: true
73
+ run: |
74
+ CODE_SANDBOXES_LIVE=1 pytest -q -m live \
75
+ tests/test_environment_live_matrix.py \
76
+ --junitxml=live-matrix-results.xml
77
+ env:
78
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
79
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
80
+ 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 }}
85
+
86
+ - name: Upload the JUnit report
87
+ if: always()
88
+ uses: actions/upload-artifact@v4
89
+ with:
90
+ name: environments-live-results
91
+ path: live-matrix-results.xml
92
+ retention-days: 14
93
+
94
+ - name: Summarize genuine failures (not xfail) naming provider and check
95
+ if: steps.live.outcome == 'failure'
96
+ run: |
97
+ python - <<'PY'
98
+ import xml.etree.ElementTree as ET
99
+
100
+ tree = ET.parse("live-matrix-results.xml")
101
+ lines = []
102
+ for testcase in tree.getroot().iter("testcase"):
103
+ # An expected-fail (E2B/Modal's own documented gaps) is reported
104
+ # by pytest's junit writer as its own case with neither
105
+ # <failure> nor <error> — only a genuine, un-marked failure
106
+ # (Daytona, or a real regression anywhere else) has one.
107
+ node = testcase.find("failure")
108
+ if node is None:
109
+ node = testcase.find("error")
110
+ if node is None:
111
+ continue
112
+ name = f"{testcase.get('classname')}::{testcase.get('name')}"
113
+ detail = (node.get("message") or node.text or "").strip()
114
+ short = "\n".join(detail.splitlines()[:20])
115
+ lines.append(f"### `{name}`\n\n```\n{short}\n```")
116
+
117
+ with open("failures.md", "w") as handle:
118
+ handle.write("\n\n".join(lines))
119
+ print(f"{len(lines)} genuine failure(s) found")
120
+ PY
121
+
122
+ - name: Ensure the environments-live label exists
123
+ if: steps.live.outcome == 'failure'
124
+ continue-on-error: true # already existing is not an error
125
+ uses: actions/github-script@v7
126
+ with:
127
+ script: |
128
+ await github.rest.issues.createLabel({
129
+ owner: context.repo.owner,
130
+ repo: context.repo.repo,
131
+ name: 'environments-live',
132
+ color: 'b60205',
133
+ description: 'The nightly Environments live matrix (E2-11)',
134
+ });
135
+
136
+ - name: Open or update an issue naming the provider and the check
137
+ if: steps.live.outcome == 'failure'
138
+ uses: actions/github-script@v7
139
+ with:
140
+ script: |
141
+ const fs = require('fs');
142
+ const detail = fs.readFileSync('failures.md', 'utf8').trim();
143
+ if (!detail) {
144
+ // Every failure was inside an `xfail` — nothing genuinely
145
+ // broke (see the module docstring's own reasoning).
146
+ return;
147
+ }
148
+ const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
149
+ const body = [
150
+ `The nightly [environments live matrix](${runUrl}) failed for real — not the two already-documented \`xfail\` gaps (E2-03's uid/gid, Modal's missing \`setpriv\`).`,
151
+ '',
152
+ detail,
153
+ ].join('\n');
154
+ const title = 'environments-live: nightly failure';
155
+ const existing = await github.rest.issues.listForRepo({
156
+ owner: context.repo.owner,
157
+ repo: context.repo.repo,
158
+ state: 'open',
159
+ labels: 'environments-live',
160
+ });
161
+ const found = existing.data.find((issue) => issue.title === title);
162
+ if (found) {
163
+ await github.rest.issues.createComment({
164
+ owner: context.repo.owner,
165
+ repo: context.repo.repo,
166
+ issue_number: found.number,
167
+ body,
168
+ });
169
+ } else {
170
+ await github.rest.issues.create({
171
+ owner: context.repo.owner,
172
+ repo: context.repo.repo,
173
+ title,
174
+ body,
175
+ labels: ['environments-live'],
176
+ });
177
+ }
178
+
179
+ - name: Fail the job if something genuinely broke
180
+ if: steps.live.outcome == 'failure'
181
+ run: exit 1
@@ -37,7 +37,7 @@ jobs:
37
37
  uses: ./.github/workflows/reusable-python.yml
38
38
  with:
39
39
  python-version: ${{ matrix.python-version }}
40
- install-extras: test
40
+ install-extras: test,datalayer
41
41
  extra-packages: dbus-python keyring secretstorage
42
42
  run-tests: true
43
43
  test-command: pytest tests/ --cov=code_sandboxes --cov-report term-missing
@@ -9,6 +9,11 @@ repos:
9
9
  - id: check-case-conflict
10
10
  - id: check-executables-have-shebangs
11
11
  - id: requirements-txt-fixer
12
+ # Datalayer's protected constraints (PLAN_ENV.md, E1-04): the header
13
+ # explains why it is ordered the way it is, and the fixer's sort
14
+ # would push that header below a package line rather than order the
15
+ # packages beneath it.
16
+ exclude: ^code_sandboxes/environments/constraints/sandbox-contract-v1\.txt$
12
17
  - id: check-added-large-files
13
18
  - id: check-case-conflict
14
19
  - id: check-toml
@@ -8,11 +8,108 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.9.1
12
+
13
+ - **mTLS to the build pool's `buildkitd`** (`environments.adapters.datalayer.Builder`,
14
+ `environments.resolve.BuildkitResolveRunner`; PLAN_ENV.md E1-06/E1-07). Both
15
+ drivers of `buildctl` took only `--addr`, which is what every prior live
16
+ drill needed against a plain-socket, ephemeral `buildkitd` — never a real
17
+ one. The build pool's real daemon on r1 takes mTLS connections only, so
18
+ reaching it needed three more flags neither driver had: `--tlscert`,
19
+ `--tlskey` and `--tlscacert`, each read from `DATALAYER_BUILDKIT_TLSCERT`,
20
+ `DATALAYER_BUILDKIT_TLSKEY` and `DATALAYER_BUILDKIT_TLSCACERT` when not
21
+ passed explicitly (`Builder`, matching how it already reads
22
+ `DATALAYER_BUILDKIT_ADDR`) or passed in by the caller (`BuildkitResolveRunner`,
23
+ constructed by durable's own `activities_environments.py`, which now reads
24
+ and forwards the same three). All three or none — a partial set is treated
25
+ as none, since a `buildkitd` requiring mTLS refuses a client carrying only
26
+ some of them at the daemon, with a less useful error than refusing here.
27
+
28
+ ## 1.9.0
29
+
30
+ - **The Daytona builder** (`environments.adapters.daytona`, PLAN_ENV.md
31
+ E2-04). A snapshot built from the approved Datalayer base — `uv pip sync --require-hashes` against the resolved lock, your files, `postInstall`
32
+ commands, and an explicit `tini -- sleep infinity` entrypoint — with the
33
+ owner's own Daytona organization, never this package's ambient
34
+ credentials. Confirmed live against a real, hash-verified lock: a real
35
+ snapshot reaches `Active` and a sandbox launched from it passes the full
36
+ sandbox contract, doctor included — the one managed variant, of the
37
+ three landing here, whose build-time state and launched state fully
38
+ agree.
39
+ - **The E2B builder** (`environments.adapters.e2b`, PLAN_ENV.md E2-03). A
40
+ template built from `code-interpreter-v1` — E2B's own proprietary
41
+ code-interpreter server ships baked into it only, so the build starts
42
+ there rather than from the Datalayer base and reconciles whatever it
43
+ shipped to exactly what the lock pins. Two live-found identity bugs are
44
+ fixed: `code-interpreter-v1` already holds an account at uid 1000 and a
45
+ group at gid 100 of its own, so the old `useradd` was silently landing
46
+ elsewhere — the build now renames the existing account instead — and the
47
+ base sets no locale at all, now set explicitly. A real build passes the
48
+ contract's doctor check in full. **Still open:** a launched sandbox
49
+ currently runs as root, not the contract's `1000:100` — the two
50
+ `systemd` services that actually execute a launched sandbox's code have
51
+ no `User=` of their own, and E2B's private server hardcodes `/home/user`
52
+ as the working directory, independent of anything the build sets.
53
+ - **The Modal builder** (`environments.adapters.modal`, PLAN_ENV.md E2-05).
54
+ An image built `from_aws_ecr` off the approved base, with a per-build
55
+ Secret made and torn down around it. The contract's own identity is put
56
+ back at launch: `modal_sandbox.py`'s session driver now drops itself to
57
+ `1000:100` for a sandbox launched from a built Environments artifact
58
+ specifically (gated on `image_id`, so general Modal sandbox usage
59
+ elsewhere is unaffected) — Modal ignores the image's own `USER`, so this
60
+ is the only place it can be restored. A separate, previously
61
+ silently-fatal bug is also fixed: the real launcher creates the sandbox
62
+ with no command arguments at all, so an entrypoint relying on `exec "$@"` alone was a no-op and every launched environment exited within
63
+ seconds; the entrypoint now falls back to `sleep infinity` when given
64
+ none. **Still open:** two of the core tier's checks (imports, filesystem)
65
+ still fail on a launched sandbox, for a reason not yet root-caused —
66
+ confirmed specific to a contract-built image under repeated `exec`, not
67
+ the identity fix.
68
+ - **A real, shared bug in all three managed launchers, found in review**:
69
+ `start()` creates the remote sandbox well before it marks itself
70
+ started, while `stop()` was guarded on that flag rather than on the
71
+ resource itself — a failure in between left a real, running sandbox
72
+ that `stop()` skipped entirely, orphaned for good. Fixed identically in
73
+ `daytona_sandbox.py`, `modal_sandbox.py` and `e2b_sandbox.py`.
74
+ - **A nightly live matrix for the three managed builders**
75
+ (`.github/workflows/environments-live.yml`, PLAN_ENV.md E2-11). Builds a
76
+ real artifact on each real provider from a real, hash-verified lock,
77
+ launches a sandbox from it, and runs the formal core tier — opening (or
78
+ commenting on) an issue naming the provider and the check on a genuine
79
+ failure. E2B and Modal run `xfail(strict=False)` for their own
80
+ already-documented gaps above; Daytona carries no such marker. Needs
81
+ provider secrets added to the repository before it does anything for
82
+ real.
83
+
84
+ ## 1.8.2
85
+
86
+ - **A sandbox can be asked whether it is still alive, and the answer no longer
87
+ comes from a flag we set ourselves.** `CodeSandboxClient.is_alive()` returned
88
+ `is_started`, which records that `start()` ran in this process and nothing
89
+ else, so it kept answering `True` after the backend was gone. `Sandbox` now
90
+ carries an `is_alive()` that variants override; the Jupyter Server variant
91
+ looks its kernel id up on the server the way `_find_existing_kernel` lists
92
+ them, and the client asks the sandbox instead of reading its own flag.
93
+ Variants that have no way to ask their provider inherit the base answer,
94
+ `is_started`, which is all they can honestly say.
95
+ - **Build secrets** (`environments.build_secrets`, PLAN_ENV.md E3-05).
96
+ `spec.buildSecrets` now resolves for real on the Datalayer variant: each
97
+ declared secret's value is fetched from IAM's own internal route at the
98
+ moment the `postInstall` step runs, mounted with BuildKit's
99
+ `--mount=type=secret` (an environment variable or a file under
100
+ `/run/secrets/`, per `mountAs`) in its own directory outside the build
101
+ context, and passed to `buildctl` by file path — never in argv, never in a
102
+ step result, never an `ARG`/`ENV` that would bake it into the image's
103
+ history. E2B and Daytona refuse a spec naming one outright: E0-04's spike
104
+ found only a registry login for the private base on either, never a
105
+ per-step arbitrary secret. A version with any build secret can never be
106
+ published to the Library (D-12). New codes `DL_ENV_BUILD_SECRET_UNAVAILABLE`
107
+ and `DL_ENV_PUBLICATION_BLOCKED`.
108
+
11
109
  ## 1.8.0
12
110
 
13
111
  - **The Datalayer builder** (`environments.adapters.datalayer`, PLAN_ENV.md
14
- E1-07). The Dockerfile is generated from the lock — `uv pip sync
15
- --require-hashes`, apt at the versions the lock recorded, `env` before
112
+ E1-07). The Dockerfile is generated from the lock — `uv pip sync --require-hashes`, apt at the versions the lock recorded, `env` before
16
113
  anything installs, `postInstall` as uid 1000 with no network — and the push
17
114
  is by digest with an SBOM and provenance attestation, under the operability
18
115
  tag `v<n>-<build_uid>` so a retried build cannot collide with the attempt
@@ -80,8 +177,7 @@
80
177
  pinned exceptions left.
81
178
 
82
179
  - A Datalayer sandbox says when it is running code, so an interrupt can reach
83
- it. `Sandbox.interrupt` refuses before it delegates — `if not
84
- self._executing_event.is_set(): return False` — and `run_code` never set that
180
+ it. `Sandbox.interrupt` refuses before it delegates — `if not self._executing_event.is_set(): return False` — and `run_code` never set that
85
181
  event, so `is_executing` was always False, every interrupt was refused at the
86
182
  door, and the refusal was reported as "no code was running" to a caller
87
183
  watching a cell run.
@@ -54,6 +54,11 @@ live-matrix:
54
54
  kill-relaunch:
55
55
  CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_the_kill_relaunch_drill.py
56
56
 
57
+ ## The managed Environment builders against the real providers: a real
58
+ ## resolved lock, build, launch, and the formal core tier (E2-11).
59
+ environments-live:
60
+ CODE_SANDBOXES_LIVE=1 pytest -q -m live tests/test_environment_live_matrix.py
61
+
57
62
  clean: ## clean
58
63
  git clean -fdx
59
64
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.8.0
3
+ Version: 1.9.1
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
@@ -75,6 +75,7 @@ Provides-Extra: e2b
75
75
  Requires-Dist: e2b-code-interpreter>=2.9.1; extra == 'e2b'
76
76
  Provides-Extra: environments-builder
77
77
  Requires-Dist: boto3>=1.34; extra == 'environments-builder'
78
+ Requires-Dist: httpx>=0.27; extra == 'environments-builder'
78
79
  Provides-Extra: google-colab
79
80
  Provides-Extra: kaggle
80
81
  Requires-Dist: kaggle>=1.6; extra == 'kaggle'
@@ -87,6 +88,7 @@ Requires-Dist: modal>=1.5.2; extra == 'modal'
87
88
  Provides-Extra: monty
88
89
  Requires-Dist: pydantic-monty; extra == 'monty'
89
90
  Provides-Extra: test
91
+ Requires-Dist: httpx>=0.27; extra == 'test'
90
92
  Requires-Dist: ipykernel; extra == 'test'
91
93
  Requires-Dist: jupyter-server<3,>=1.6; extra == 'test'
92
94
  Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
@@ -107,7 +109,7 @@ Description-Content-Type: text/markdown
107
109
  [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
108
110
  [![PyPI - Version](https://img.shields.io/pypi/v/code-sandboxes)](https://pypi.org/project/code-sandboxes)
109
111
 
110
- # { } 📦 Code Sandboxes
112
+ # 📦 Code Sandboxes
111
113
 
112
114
  Code Sandboxes (`code_sandboxes`) is a Python package for running code in isolated sandbox variants through a unified API.
113
115
 
@@ -9,7 +9,7 @@
9
9
  [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
10
10
  [![PyPI - Version](https://img.shields.io/pypi/v/code-sandboxes)](https://pypi.org/project/code-sandboxes)
11
11
 
12
- # { } 📦 Code Sandboxes
12
+ # 📦 Code Sandboxes
13
13
 
14
14
  Code Sandboxes (`code_sandboxes`) is a Python package for running code in isolated sandbox variants through a unified API.
15
15
 
@@ -3,4 +3,4 @@
3
3
 
4
4
  """Code Sandboxes."""
5
5
 
6
- __version__ = "1.8.0"
6
+ __version__ = "1.9.1"
@@ -198,6 +198,17 @@ class Sandbox(ABC):
198
198
  """Check if sandbox has been started."""
199
199
  return self._started
200
200
 
201
+ def is_alive(self) -> bool:
202
+ """Check whether the sandbox is still usable.
203
+
204
+ Distinct from :attr:`is_started`, which only records that `start` ran
205
+ in this process: a sandbox can be culled, evicted or shut down by
206
+ whoever else holds it, and nothing tells us. The base answer is
207
+ :attr:`is_started`, all a variant with no way to ask its provider can
208
+ honestly say; variants that can ask override this.
209
+ """
210
+ return self.is_started
211
+
201
212
  @property
202
213
  def is_executing(self) -> bool:
203
214
  """Check if the sandbox is currently executing code."""
@@ -598,7 +609,7 @@ class Sandbox(ABC):
598
609
  # caller passed itself wins, so naming both is not a surprise.
599
610
  from .environments.builders import launch_arguments
600
611
 
601
- for name_, value in launch_arguments(artifact).items():
612
+ for name_, value in launch_arguments(artifact, expected_variant=variant_value).items():
602
613
  kwargs.setdefault(name_, value)
603
614
 
604
615
  if variant_value == "eval":
@@ -1078,7 +1089,7 @@ class Sandbox(ABC):
1078
1089
  from .exceptions import VariableNotFoundError
1079
1090
 
1080
1091
  raise VariableNotFoundError(name)
1081
- return _json.loads(printed[start + len(self._STDOUT_START):end])
1092
+ return _json.loads(printed[start + len(self._STDOUT_START) : end])
1082
1093
 
1083
1094
  def set_variable(self, name: str, value: Any, context: Context | None = None) -> None:
1084
1095
  """Set a variable in the sandbox.
@@ -497,7 +497,14 @@ class CodeSandboxClient:
497
497
  return self._sandbox.interrupt()
498
498
 
499
499
  def is_alive(self) -> bool:
500
- """Whether the sandbox is started and available for execution."""
500
+ """Whether the wrapped sandbox is still available for execution.
501
+
502
+ Asks the sandbox, which may check its provider. Sandboxes that expose
503
+ no liveness hook fall back to whether they report themselves started.
504
+ """
505
+ is_alive_fn = getattr(self._sandbox, "is_alive", None)
506
+ if callable(is_alive_fn):
507
+ return bool(is_alive_fn())
501
508
  return self.is_started
502
509
 
503
510
  def restart(self) -> None:
@@ -284,7 +284,7 @@ print({_ENVELOPE_START!r} + __cmd_json__.dumps(__cmd_output__) + {_ENVELOPE_END!
284
284
  duration=time.time() - start_time,
285
285
  )
286
286
  try:
287
- result = json.loads(printed[start + len(_ENVELOPE_START):end])
287
+ result = json.loads(printed[start + len(_ENVELOPE_START) : end])
288
288
  return CommandResult(
289
289
  exit_code=int(result["exit_code"]),
290
290
  stdout=str(result["stdout"]),
@@ -55,7 +55,6 @@ from .models import (
55
55
  SnapshotInfo,
56
56
  )
57
57
 
58
-
59
58
  logger = logging.getLogger(__name__)
60
59
 
61
60
 
@@ -580,10 +579,11 @@ class DatalayerSandbox(Sandbox):
580
579
  **This variant neither implemented it nor read the flag**, so the
581
580
  base class's default ran instead: it sets a flag, returns `True`, and
582
581
  stops nothing. (`google_colab` and `kaggle` are entitled to that
583
- default — they poll `_interrupt_requested` and stop cooperatively.) Everything above believed it. `tasks/cancel` marks a
584
- task `cancelled` and calls the interrupt registered by
585
- `execute_cell`; the interrupt answered yes; the cell went on running
586
- on a runtime that goes on being billed.
582
+ default — they poll `_interrupt_requested` and stop cooperatively.)
583
+ Everything above believed it. `tasks/cancel` marks a task `cancelled`
584
+ and calls the interrupt registered by `execute_cell`; the interrupt
585
+ answered yes; the cell went on running on a runtime that goes on
586
+ being billed.
587
587
 
588
588
  Measured on prod1 on 2026-09-07: a cell looping for two minutes,
589
589
  cancelled ten seconds in, `tasks/cancel` and `tasks/get` both
@@ -603,13 +603,11 @@ class DatalayerSandbox(Sandbox):
603
603
  """
604
604
  client = getattr(self._runtime, "sandbox_client", None)
605
605
  if client is None:
606
- logger.warning(
607
- "This sandbox has no runtime client, so nothing could be interrupted"
608
- )
606
+ logger.warning("This sandbox has no runtime client, so nothing could be interrupted")
609
607
  return False
610
608
  try:
611
609
  return bool(client.interrupt())
612
- except Exception as error: # noqa: BLE001 - a failed interrupt is an answer
610
+ except Exception as error:
613
611
  logger.warning("The runtime's kernel could not be interrupted: %s", error)
614
612
  return False
615
613
 
@@ -817,7 +815,9 @@ class DatalayerSandbox(Sandbox):
817
815
  try:
818
816
  # Set environment variables if provided
819
817
  if envs:
820
- env_code = "\n".join(f"import os; os.environ[{k!r}] = {v!r}" for k, v in envs.items())
818
+ env_code = "\n".join(
819
+ f"import os; os.environ[{k!r}] = {v!r}" for k, v in envs.items()
820
+ )
821
821
  self._runtime.execute(env_code)
822
822
 
823
823
  response = self._runtime.execute(code, timeout=execution_timeout)
@@ -655,17 +655,21 @@ class DaytonaSandbox(Sandbox):
655
655
  )
656
656
 
657
657
  def stop(self) -> None:
658
- if not self._started:
658
+ # Guarded on the resource itself, not `_started` (found in review):
659
+ # `start()` creates the remote sandbox well before it marks itself
660
+ # started, so a failure in between — `create_context`, building
661
+ # `SandboxInfo` — would otherwise leave a real, running sandbox that
662
+ # `stop()` skips entirely and this object can never clean up again.
663
+ if self._sandbox is None:
659
664
  return
660
- if self._sandbox is not None:
661
- try:
662
- if self._delete_on_stop:
663
- self._sandbox.delete()
664
- else:
665
- self._sandbox.stop()
666
- except Exception:
667
- logger.debug("Ignoring error while stopping the Daytona sandbox", exc_info=True)
668
- self._sandbox = None
665
+ try:
666
+ if self._delete_on_stop:
667
+ self._sandbox.delete()
668
+ else:
669
+ self._sandbox.stop()
670
+ except Exception:
671
+ logger.debug("Ignoring error while stopping the Daytona sandbox", exc_info=True)
672
+ self._sandbox = None
669
673
  self._daytona = None
670
674
  self._jupyter_endpoint = None
671
675
  self._contexts.clear()
@@ -10,19 +10,18 @@ through `jupyter-kernel-client` to execute code.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import logging
13
14
  import os
14
15
  import tempfile
15
16
  import time
16
17
  import uuid
17
18
 
18
- import logging
19
-
20
19
  import requests
21
20
 
22
21
  from .base import Sandbox, marks_execution
23
- from .jupyter_ingress import interrupt_kernel_client
24
22
  from .exceptions import SandboxConfigurationError, SandboxNotStartedError
25
23
  from .interfaces import ISandboxClient
24
+ from .jupyter_ingress import interrupt_kernel_client
26
25
  from .models import (
27
26
  CodeError,
28
27
  Context,
@@ -419,14 +419,18 @@ class E2BSandbox(Sandbox):
419
419
  return self._allow_internet_access
420
420
 
421
421
  def stop(self) -> None:
422
- if not self._started:
422
+ # Guarded on the resource itself, not `_started` (found in review):
423
+ # `start()` creates the remote sandbox well before it marks itself
424
+ # started, so a failure in between — `create_context`, building
425
+ # `SandboxInfo` — would otherwise leave a real, running sandbox that
426
+ # `stop()` skips entirely and this object can never clean up again.
427
+ if self._sandbox is None:
423
428
  return
424
- if self._sandbox is not None:
425
- try:
426
- self._sandbox.kill()
427
- except Exception:
428
- logger.debug("Ignoring error while killing the E2B sandbox", exc_info=True)
429
- self._sandbox = None
429
+ try:
430
+ self._sandbox.kill()
431
+ except Exception:
432
+ logger.debug("Ignoring error while killing the E2B sandbox", exc_info=True)
433
+ self._sandbox = None
430
434
  self._contexts.clear()
431
435
  self._jupyter_endpoint = None
432
436
  self._volume_mounts.stopped()
@@ -527,11 +531,19 @@ class E2BSandbox(Sandbox):
527
531
  on_result(translated)
528
532
 
529
533
  seconds = timeout if timeout is not None else self.config.timeout
534
+ # A language and a context are two ways of saying the same thing to
535
+ # E2B, and its SDK refuses both at once: "You can provide context or
536
+ # language, but not both at the same time." A context already carries
537
+ # the language it was created with, so the context wins when there is
538
+ # one — which is what check 14 of E0-04 found here (PLAN_ENV E2-02).
539
+ e2b_context = self._e2b_context(context)
540
+ chosen: dict[str, Any] = (
541
+ {"context": e2b_context} if e2b_context is not None else {"language": "python"}
542
+ )
530
543
  try:
531
544
  execution = self._sandbox.run_code(
532
545
  code,
533
- language="python",
534
- context=self._e2b_context(context),
546
+ **chosen,
535
547
  on_stdout=take_stdout,
536
548
  on_stderr=take_stderr,
537
549
  on_result=take_result,
@@ -16,6 +16,7 @@ provider-specific:
16
16
  - :mod:`.spec` — the canonical specification and its rules;
17
17
  - :mod:`.resolve` — the lock a version is resolved into, once (D-9);
18
18
  - :mod:`.policy` — whether an artifact's scan lets it be used (D-11);
19
+ - :mod:`.accounts` — whose provider account a managed artifact lives in (D-8);
19
20
  - :mod:`.attest` — the scan and the signature it needs before anything runs it;
20
21
  - :mod:`.contract` — ``sandbox-contract/v1`` and the Dockerfile validator;
21
22
  - :mod:`.lifecycle` — the version states and their legal moves;
@@ -30,6 +31,7 @@ The builders themselves are in :mod:`.adapters`, loaded by variant.
30
31
 
31
32
  from __future__ import annotations
32
33
 
34
+ from .accounts import account_fingerprints, fingerprint_matches, provider_account
33
35
  from .attest import Attestor, attest_artifact, signature_tag
34
36
  from .bases import APPROVED_BASES, ApprovedBase
35
37
  from .builders import (
@@ -106,6 +108,7 @@ __all__ = [
106
108
  "ScanPolicy",
107
109
  "ValidationResult",
108
110
  "VersionState",
111
+ "account_fingerprints",
109
112
  "attest_artifact",
110
113
  "build_outcome",
111
114
  "can_transition",
@@ -113,6 +116,7 @@ __all__ = [
113
116
  "canonical_json",
114
117
  "check_dockerfile",
115
118
  "decide",
119
+ "fingerprint_matches",
116
120
  "get_builder",
117
121
  "locked_versions",
118
122
  "map_provider_error",
@@ -120,6 +124,7 @@ __all__ = [
120
124
  "parse_environment",
121
125
  "parse_resolver_failure",
122
126
  "protected_pins",
127
+ "provider_account",
123
128
  "refuse_if_blocked",
124
129
  "resolve_environment",
125
130
  "signature_tag",