code-sandboxes 1.9.21__tar.gz → 1.9.26__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 (265) hide show
  1. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/PKG-INFO +1 -1
  2. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/__version__.py +1 -1
  3. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/datalayer.py +19 -1
  4. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/daytona.py +114 -4
  5. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/managed.py +18 -1
  6. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/modal.py +101 -14
  7. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/attest.py +70 -14
  8. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/conformance.py +31 -3
  9. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/policy.py +133 -1
  10. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/spec.py +116 -8
  11. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/manage.py +80 -6
  12. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/modal_sandbox.py +14 -2
  13. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_attest.py +85 -1
  14. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_datalayer_builder.py +29 -4
  15. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_daytona_builder.py +157 -0
  16. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_live_matrix.py +20 -2
  17. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_managed_builders.py +30 -9
  18. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_modal_builder.py +117 -7
  19. code_sandboxes-1.9.26/tests/test_environment_organization_policy.py +165 -0
  20. code_sandboxes-1.9.26/tests/test_environment_spec_organization_policy.py +198 -0
  21. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_manage.py +68 -0
  22. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_manage_datalayer.py +2 -3
  23. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/build.yml +0 -0
  24. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/environments-live.yml +0 -0
  25. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-code-style.yml +0 -0
  26. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-tests.yml +0 -0
  27. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-typing.yml +0 -0
  28. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/reusable-python.yml +0 -0
  29. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.gitignore +0 -0
  30. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.licenserc.yaml +0 -0
  31. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.pre-commit-config.yaml +0 -0
  32. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.vscode/settings.json +0 -0
  33. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/CHANGELOG.md +0 -0
  34. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/Dockerfile +0 -0
  35. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/LICENSE +0 -0
  36. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/Makefile +0 -0
  37. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/README.md +0 -0
  38. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/__init__.py +0 -0
  39. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/base.py +0 -0
  40. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/bridge_mount.py +0 -0
  41. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/builds.py +0 -0
  42. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/cli.py +0 -0
  43. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/client.py +0 -0
  44. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/cloudflare_sandbox.py +0 -0
  45. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/commands.py +0 -0
  46. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/console.py +0 -0
  47. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/contents.py +0 -0
  48. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/coreweave_sandbox.py +0 -0
  49. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/datalayer_sandbox.py +0 -0
  50. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/daytona_sandbox.py +0 -0
  51. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/docker_sandbox.py +0 -0
  52. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/e2b_sandbox.py +0 -0
  53. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/__init__.py +0 -0
  54. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/__main__.py +0 -0
  55. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/accounts.py +0 -0
  56. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/__init__.py +0 -0
  57. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/e2b.py +0 -0
  58. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/bases.py +0 -0
  59. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/breaker.py +0 -0
  60. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/build_secrets.py +0 -0
  61. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/builders.py +0 -0
  62. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/canonical.py +0 -0
  63. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  64. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  65. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  66. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/contract.py +0 -0
  67. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/__init__.py +0 -0
  68. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/build.py +0 -0
  69. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  70. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  71. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/errors.py +0 -0
  72. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/files.py +0 -0
  73. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/image_import.py +0 -0
  74. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/lifecycle.py +0 -0
  75. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/redact.py +0 -0
  76. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/resolve.py +0 -0
  77. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/resolve_conda.py +0 -0
  78. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/schema.py +0 -0
  79. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/eval_sandbox.py +0 -0
  80. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/exceptions.py +0 -0
  81. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/filesystem.py +0 -0
  82. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/google_colab.py +0 -0
  83. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/google_colab_sandbox.py +0 -0
  84. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/interfaces.py +0 -0
  85. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/jupyter_ingress.py +0 -0
  86. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  87. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle.py +0 -0
  88. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_execute.py +0 -0
  89. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_live.py +0 -0
  90. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_sandbox.py +0 -0
  91. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/lifecycle.py +0 -0
  92. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/models.py +0 -0
  93. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/monty_sandbox.py +0 -0
  94. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/provider_ingress.py +0 -0
  95. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/providers.py +0 -0
  96. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docker/Dockerfile +0 -0
  97. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/.gitignore +0 -0
  98. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/LICENSE +0 -0
  99. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/Makefile +0 -0
  100. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/README.md +0 -0
  101. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/babel.config.js +0 -0
  102. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/api-reference/_category_.yml +0 -0
  103. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/api-reference/index.mdx +0 -0
  104. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/cli/_category_.yml +0 -0
  105. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/cli/index.mdx +0 -0
  106. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/contribute/_category_.yml +0 -0
  107. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/contribute/index.mdx +0 -0
  108. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/_category_.yml +0 -0
  109. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/contract.mdx +0 -0
  110. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/index.mdx +0 -0
  111. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/specification.mdx +0 -0
  112. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/spike-2026-09.mdx +0 -0
  113. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/examples/_category_.yml +0 -0
  114. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/examples/index.mdx +0 -0
  115. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/guide/_category_.yml +0 -0
  116. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/guide/index.mdx +0 -0
  117. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/index.mdx +0 -0
  118. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/install/_category_.yml +0 -0
  119. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/install/index.mdx +0 -0
  120. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/provider-ingress.mdx +0 -0
  121. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/_category_.yml +0 -0
  122. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/cloudflare.mdx +0 -0
  123. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/coreweave.mdx +0 -0
  124. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/datalayer.mdx +0 -0
  125. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/daytona.mdx +0 -0
  126. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/docker.mdx +0 -0
  127. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/e2b.mdx +0 -0
  128. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/eval.mdx +0 -0
  129. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/google-colab.mdx +0 -0
  130. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/index.mdx +0 -0
  131. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/jupyter-server.mdx +0 -0
  132. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/kaggle.mdx +0 -0
  133. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/modal.mdx +0 -0
  134. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/monty.mdx +0 -0
  135. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docusaurus.config.js +0 -0
  136. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/package.json +0 -0
  137. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/sidebars.js +0 -0
  138. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow1.js +0 -0
  139. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow1.module.css +0 -0
  140. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow2.js +0 -0
  141. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow2.module.css +0 -0
  142. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow3.js +0 -0
  143. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow3.module.css +0 -0
  144. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/css/custom.css +0 -0
  145. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/pages/index.module.css +0 -0
  146. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/pages/markdown-page.md +0 -0
  147. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/theme/CustomDocItem.tsx +0 -0
  148. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/theme/IconsTitle.tsx +0 -0
  149. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/bricks.svg +0 -0
  150. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/building-construction.svg +0 -0
  151. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/cloud-sun.svg +0 -0
  152. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/cloud.svg +0 -0
  153. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/collaboration.svg +0 -0
  154. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/copy-left.svg +0 -0
  155. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/logo.png +0 -0
  156. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/logo.svg +0 -0
  157. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/business.svg +0 -0
  158. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  159. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/devops.svg +0 -0
  160. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/project/project-line.svg +0 -0
  161. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/favicon.ico +0 -0
  162. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/jupyter.svg +0 -0
  163. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/open-source.svg +0 -0
  164. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/react-js.svg +0 -0
  165. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/react-js2.svg +0 -0
  166. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/ringed-planet.svg +0 -0
  167. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/robot.svg +0 -0
  168. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/rocket.svg +0 -0
  169. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/shield.svg +0 -0
  170. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/sparkles.svg +0 -0
  171. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/streamlit.gif +0 -0
  172. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/super-hero.svg +0 -0
  173. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/target.svg +0 -0
  174. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/thumbs-up.svg +0 -0
  175. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/README.md +0 -0
  176. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/Makefile +0 -0
  177. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/cloudflare_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/coreweave_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/datalayer_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/daytona_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/docker_sandbox_example.py +0 -0
  182. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/e2b_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/eval_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/google_colab_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/kaggle_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/modal_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/monty_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/Makefile +0 -0
  190. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/cloudflare_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/coreweave_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/datalayer_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/daytona_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/docker_sandbox_example.py +0 -0
  195. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/e2b_sandbox_example.py +0 -0
  196. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/eval_sandbox_example.py +0 -0
  197. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/google_colab_sandbox_example.py +0 -0
  198. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  199. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/kaggle_sandbox_example.py +0 -0
  200. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/modal_sandbox_example.py +0 -0
  201. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/monty_sandbox_example.py +0 -0
  202. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/pyproject.toml +0 -0
  203. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/schemas/environment-v1alpha1.json +0 -0
  204. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/__init__.py +0 -0
  205. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/conftest.py +0 -0
  206. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  207. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_bridge_mount.py +0 -0
  208. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_builds.py +0 -0
  209. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cli_exec.py +0 -0
  210. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cli_repl.py +0 -0
  211. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client.py +0 -0
  212. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_contents.py +0 -0
  213. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_files.py +0 -0
  214. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_outcome_error_output.py +0 -0
  215. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cloudflare.py +0 -0
  216. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_commands_envelope.py +0 -0
  217. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_console.py +0 -0
  218. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_contents_contract.py +0 -0
  219. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_contents_live_matrix.py +0 -0
  220. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_coreweave.py +0 -0
  221. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_datalayer_sandbox.py +0 -0
  222. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_daytona.py +0 -0
  223. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_e2b.py +0 -0
  224. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_accounts.py +0 -0
  225. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_bases.py +0 -0
  226. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_breaker.py +0 -0
  227. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_build_secrets.py +0 -0
  228. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_builders.py +0 -0
  229. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_canonical.py +0 -0
  230. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_conformance.py +0 -0
  231. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_constraints.py +0 -0
  232. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_contract.py +0 -0
  233. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_cross_variant_report.py +0 -0
  234. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_doctor.py +0 -0
  235. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_e2b_builder.py +0 -0
  236. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_errors.py +0 -0
  237. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_image_import.py +0 -0
  238. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_lifecycle.py +0 -0
  239. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_redact.py +0 -0
  240. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_resolve.py +0 -0
  241. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_resolve_conda.py +0 -0
  242. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_spec.py +0 -0
  243. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_eval.py +0 -0
  244. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  245. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_factory.py +0 -0
  246. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_google_colab.py +0 -0
  247. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_integration.py +0 -0
  248. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_ingress.py +0 -0
  249. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_server.py +0 -0
  250. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_server_streaming.py +0 -0
  251. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle.py +0 -0
  252. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_execute.py +0 -0
  253. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_live.py +0 -0
  254. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_session.py +0 -0
  255. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kernel_client_compatibility.py +0 -0
  256. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_lifecycle_vocabulary.py +0 -0
  257. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_modal_google_colab_sandbox.py +0 -0
  258. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_modal_session.py +0 -0
  259. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_models.py +0 -0
  260. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_output_line_fidelity.py +0 -0
  261. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_provider_catalog_names.py +0 -0
  262. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_provider_ingress.py +0 -0
  263. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_providers.py +0 -0
  264. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_sandboxes.py +0 -0
  265. {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_the_kill_relaunch_drill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.9.21
3
+ Version: 1.9.26
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.21"
6
+ __version__ = "1.9.26"
@@ -112,6 +112,24 @@ DEFAULT_MAX_ARTIFACT_BYTES = 20 * 1024**3
112
112
  # does; nothing about their behavior changed.
113
113
 
114
114
 
115
+ #: The Dockerfile frontend the generated build asks for.
116
+ #:
117
+ #: At least **1.10**, because `_secret_mount` renders `mountAs: env` as
118
+ #: `--mount=type=secret,env=NAME` and that key does not exist before it: 1.7,
119
+ #: which this pinned for its first year, answers `unexpected key 'env' in
120
+ #: 'env=NAME'` and fails the whole build. The mount and the pin were written
121
+ #: against each other's assumptions and never ran together, because no build
122
+ #: had ever carried a build secret — found on r1 on 2026-09-17 by the first
123
+ #: one that did, and checked against both frontends before changing it.
124
+ #:
125
+ #: Raising this is a change to every Datalayer build, so it is one constant
126
+ #: with one reason, not a literal inside the Dockerfile's own text.
127
+ DOCKERFILE_FRONTEND = "docker/dockerfile:1.10"
128
+
129
+ #: The first frontend with `--mount=type=secret,env=` (see above).
130
+ SECRET_ENV_FRONTEND = (1, 10)
131
+
132
+
115
133
  def _secret_mount(secret: BuildSecret) -> str:
116
134
  """The ``--mount=type=secret`` flag one `BuildSecret` renders as (E3-05).
117
135
 
@@ -283,7 +301,7 @@ class Builder:
283
301
  spec = request.environment.spec
284
302
  apt = apt_pins_in(request.lock_text)
285
303
  lines = [
286
- "# syntax=docker/dockerfile:1.7",
304
+ f"# syntax={DOCKERFILE_FRONTEND}",
287
305
  f"# Generated by Datalayer for {request.environment.metadata.name} "
288
306
  f"v{request.version}, build {request.build_uid}.",
289
307
  f"# Lock: {request.lock_digest}",
@@ -90,7 +90,7 @@ from __future__ import annotations
90
90
  import shlex
91
91
  import tempfile
92
92
  import uuid
93
- from collections.abc import Callable
93
+ from collections.abc import Callable, Sequence
94
94
  from pathlib import Path
95
95
  from typing import Any
96
96
 
@@ -100,6 +100,7 @@ from ..builders import (
100
100
  ArtifactReference,
101
101
  BuildRequest,
102
102
  CapabilityFinding,
103
+ ValidationResult,
103
104
  )
104
105
  from ..contract import SANDBOX_CONTRACT_V1
105
106
  from ..errors import (
@@ -177,15 +178,18 @@ class Builder(ManagedBuilder):
177
178
  variant = "daytona"
178
179
  item = "E2-04"
179
180
  title = "Daytona"
180
- #: A `packages` list and, for conda (E3-02), an `environment.yml`
181
- #: dependency file installed with `micromamba`.
181
+ #: A `packages` list, or a dependency file: a conda `environment.yml`
182
+ #: installed with `micromamba` (E3-02), and a `requirements.txt` or a
183
+ #: `pyproject.toml` with its lock (E3-01), both of which resolve to the
184
+ #: very pip lock a `packages` list does — `build` tells a conda lock from
185
+ #: a pip one and nothing finer, so nothing here is format-specific.
182
186
  build_sources = ("packages", "dependencyFile", "dockerfile")
183
187
  #: None beyond the contract's own (E3-03): `Image.from_dockerfile` keeps
184
188
  #: the Dockerfile text as it is and Daytona builds it on a real Docker
185
189
  #: builder, so the grammar it accepts is Docker's. Checked in the SDK on
186
190
  #: 2026-09-17.
187
191
  forbidden_instructions = ()
188
- dependency_formats = ("conda",)
192
+ dependency_formats = ("requirements", "pyproject", "conda")
189
193
  #: Daytona runs GPUs, on its own hardware and the owner's account (E2-17).
190
194
  #: This builder does not build one yet: see `_own_findings`.
191
195
  gpu = True
@@ -567,6 +571,112 @@ class Builder(ManagedBuilder):
567
571
  raise self._provider_error("ask whether the snapshot exists", error) from error
568
572
  return True
569
573
 
574
+ def delete(self, artifact: ArtifactReference) -> None:
575
+ """Remove the snapshot, by id; one already gone is removed (E2-18).
576
+
577
+ Deleting what is gone is a success because the collector deletes
578
+ first and marks second (E1-17): a sweep that died between the two
579
+ deletes again tomorrow, and must not be refused for having worked.
580
+
581
+ **By id, never by name.** The SDK takes either, and a name is reused
582
+ once its snapshot is deleted (E0-04) — deleting by name could remove
583
+ a later build's snapshot that inherited it.
584
+ """
585
+ sdk = self._daytona_sdk()
586
+ client = self._client(sdk)
587
+ snapshot = artifact.provider_artifact_id or artifact.immutable_reference
588
+ try:
589
+ client.snapshot.delete(snapshot)
590
+ except sdk.DaytonaNotFoundError:
591
+ self._log(f"The Daytona snapshot {snapshot} was already gone")
592
+ return
593
+ except Exception as error:
594
+ raise self._provider_error("delete the snapshot", error) from error
595
+ self._log(f"Deleted the Daytona snapshot {snapshot}")
596
+
597
+ def smoke_test(
598
+ self,
599
+ artifact: ArtifactReference,
600
+ *,
601
+ environment: Any = None,
602
+ lock_text: str | None = None,
603
+ secret_values: Sequence[str] = (),
604
+ ) -> ValidationResult:
605
+ """Launch the snapshot and run Appendix B's core tier in it (E2-04).
606
+
607
+ This box's own `Done when` asks for exactly this — "a sandbox launched
608
+ from its id passes the core tier" — and until 2026-09-17 it refused
609
+ through `ManagedBuilder`, so **no Daytona build could reach
610
+ `succeeded`**: the workflow calls this step, the snapshot was built and
611
+ live at the provider, and the build was recorded failed.
612
+
613
+ **Launched by id, never by name** (E0-04): a Daytona sandbox record
614
+ keeps the snapshot's *name*, and a name is republished, so only the id
615
+ says which artifact actually ran.
616
+
617
+ **Restarted by stopping and starting the sandbox**, not the kernel
618
+ (E0-04 again): Daytona's own daemon is PID 1 here, so there is no
619
+ kernel to restart, and check 8 means the thing that survives a real
620
+ restart.
621
+
622
+ The sandbox is deleted whether the tier passed or not — a smoke test
623
+ that leaves a sandbox running bills the owner for a check.
624
+ """
625
+ if environment is None:
626
+ raise EnvironmentsError(
627
+ CAPABILITY_UNSUPPORTED,
628
+ "A Daytona smoke test needs the version's spec: the core tier "
629
+ "asks for the Python version it declared and the packages its "
630
+ "lock pinned, and an artifact carries neither",
631
+ detail={"variant": self.variant},
632
+ )
633
+ from ..conformance import expected_packages, run_core_tier
634
+
635
+ snapshot = artifact.provider_artifact_id or artifact.immutable_reference
636
+ sandbox = self._smoke_test_sandbox(snapshot)
637
+ self._log(f"Launching {snapshot} to smoke-test it")
638
+ try:
639
+ sandbox.start()
640
+ return run_core_tier(
641
+ sandbox,
642
+ python_version=environment.spec.language.version,
643
+ expected_packages=expected_packages(environment, lock_text or ""),
644
+ secret_values=tuple(secret_values),
645
+ restart=lambda: self._restart(sandbox),
646
+ )
647
+ except EnvironmentsError:
648
+ raise
649
+ except Exception as error:
650
+ raise self._provider_error("smoke-test the snapshot", error) from error
651
+ finally:
652
+ try:
653
+ sandbox.stop()
654
+ except Exception as error:
655
+ self._log(f"The smoke-test sandbox could not be stopped: {error}")
656
+
657
+ def _smoke_test_sandbox(self, snapshot: str) -> Any:
658
+ """A sandbox of this build's own snapshot, deleted when it stops."""
659
+ from ...daytona_sandbox import DaytonaSandbox
660
+
661
+ secrets = self._provider_secrets()
662
+ return DaytonaSandbox(
663
+ api_key=secrets.get("DAYTONA_API_KEY"),
664
+ organization_id=secrets.get("DAYTONA_ORGANIZATION_ID"),
665
+ snapshot=snapshot,
666
+ delete_on_stop=True,
667
+ )
668
+
669
+ @staticmethod
670
+ def _restart(sandbox: Any) -> None:
671
+ """Check 8's restart, as Daytona can do it.
672
+
673
+ Its daemon is PID 1, so there is no kernel to restart: the sandbox
674
+ itself is stopped and started, which is the stronger version of the
675
+ same question.
676
+ """
677
+ sandbox.stop()
678
+ sandbox.start()
679
+
570
680
  def _provider_error(self, what: str, error: BaseException) -> EnvironmentsError:
571
681
  return EnvironmentsError(
572
682
  PROVIDER_ERROR,
@@ -24,6 +24,7 @@ An SDK imported at module scope would turn `validate` into a 500.
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
+ from collections.abc import Sequence
27
28
  from typing import Any, Callable
28
29
 
29
30
  from ..builders import (
@@ -261,7 +262,23 @@ class ManagedBuilder:
261
262
  def inspect(self, artifact: ArtifactReference) -> ArtifactMetadata:
262
263
  raise self._not_built("inspect an artifact")
263
264
 
264
- def smoke_test(self, artifact: ArtifactReference) -> ValidationResult:
265
+ def smoke_test(
266
+ self,
267
+ artifact: ArtifactReference,
268
+ *,
269
+ environment: Any = None,
270
+ lock_text: str | None = None,
271
+ secret_values: Sequence[str] = (),
272
+ ) -> ValidationResult:
273
+ """Launch the artifact and run Appendix B's core tier in it (E2-03/04/05).
274
+
275
+ `environment` and `lock_text` are what the core tier needs and an
276
+ artifact does not carry: the Python version the spec asks for and the
277
+ packages its lock pins. The caller has both — it read them to build —
278
+ and passes them rather than making every adapter fetch them again.
279
+ They are keyword-only and optional so a caller that has neither still
280
+ type-checks, and an adapter that needs them says so itself.
281
+ """
265
282
  raise self._not_built("smoke-test an artifact")
266
283
 
267
284
  def resolve(self, version_ref: str) -> ArtifactReference:
@@ -124,7 +124,7 @@ import contextlib
124
124
  import io
125
125
  import os
126
126
  import tempfile
127
- from collections.abc import Callable
127
+ from collections.abc import Callable, Sequence
128
128
  from pathlib import Path
129
129
  from typing import Any
130
130
 
@@ -135,6 +135,7 @@ from ..builders import (
135
135
  ArtifactReference,
136
136
  BuildRequest,
137
137
  CapabilityFinding,
138
+ ValidationResult,
138
139
  )
139
140
  from ..contract import SANDBOX_CONTRACT_V1
140
141
  from ..errors import (
@@ -535,23 +536,31 @@ class Builder(ManagedBuilder):
535
536
  return declared, values
536
537
 
537
538
  def _ecr_secret(self, sdk: Any, client: Any) -> Any | None:
538
- """A Modal Secret carrying this build's own base-reader credential (D-17, D-18).
539
-
540
- `None` when the credential carries no registry login: the base is
541
- then whatever the ambient workspace can already reach, the same
542
- fallback `_client` takes with no owner token.
539
+ """A Modal Secret carrying this build's base-reader session (D-17, D-18).
540
+
541
+ `from_aws_ecr` wants the IAM session itself — keys, token and region —
542
+ not the docker-login `AWS`/token pair Daytona and E2B take, which is
543
+ what the credential's `username`/`password` hold. So it reads
544
+ `aws_session`. Reading the login pair as IAM keys is what this did
545
+ until 2026-09-18: the worker minted `AWS`/<ECR token>, this wrote it
546
+ as `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, and AWS answered "The
547
+ security token included in the request is invalid" on the base pull,
548
+ so no Modal build through the worker could ever succeed.
549
+
550
+ `None` when the credential carries no session: the base is then
551
+ whatever the ambient workspace can already reach, the same fallback
552
+ `_client` takes with no owner token.
543
553
  """
544
- username = str(getattr(self._credential, "username", "") or "")
545
- password = str(getattr(self._credential, "password", "") or "")
546
- if not (username and password):
554
+ session = dict(getattr(self._credential, "aws_session", None) or {})
555
+ if not (session.get("AWS_ACCESS_KEY_ID") and session.get("AWS_SECRET_ACCESS_KEY")):
547
556
  return None
548
- # `from_aws_ecr` wants IAM-shaped credentials, not the docker-login
549
- # `AWS`/token pair Daytona and E2B take (found live, 2026-09-13):
550
- # the same D-17 session, read differently.
551
557
  secret = sdk.Secret.from_dict(
552
558
  {
553
- "AWS_ACCESS_KEY_ID": username,
554
- "AWS_SECRET_ACCESS_KEY": password,
559
+ **{
560
+ name: str(session[name])
561
+ for name in ("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN")
562
+ if session.get(name)
563
+ },
555
564
  "AWS_REGION": self._region,
556
565
  }
557
566
  )
@@ -665,6 +674,84 @@ class Builder(ManagedBuilder):
665
674
  raise self._provider_error("ask whether the image exists", error) from error
666
675
  return True
667
676
 
677
+ def smoke_test(
678
+ self,
679
+ artifact: ArtifactReference,
680
+ *,
681
+ environment: Any = None,
682
+ lock_text: str | None = None,
683
+ secret_values: Sequence[str] = (),
684
+ ) -> ValidationResult:
685
+ """Launch the image and run Appendix B's core tier in it (E2-05).
686
+
687
+ This box's own `Done when` asks for exactly this — "the live test
688
+ launches by image id and passes the core tier" — and it refused
689
+ through `ManagedBuilder`, so **no Modal build could reach
690
+ `succeeded`**: the image was built in the owner's workspace and the
691
+ build recorded failed at this step, the same state Daytona was in
692
+ until 2026-09-17.
693
+
694
+ **Launched by image id, never by name**: a published name is mutable
695
+ by design (§6), so only the id says which artifact ran. Launched
696
+ through `ModalSandbox` — the same class a person's launch uses —
697
+ rather than a hand-rolled `Sandbox.create`, because a hand-rolled
698
+ launch is exactly what hid, on 2026-09-13, that every image this
699
+ builder made died the instant it was launched for real.
700
+
701
+ **As the owner** (D-8): the sandbox is created with the same client
702
+ the build used, so it runs in the workspace the image is in.
703
+
704
+ **Restarted by replacing the sandbox.** Modal has no in-place
705
+ restart, so check 7's restart stops this sandbox and starts another
706
+ from the same image, which is the stronger form of the question.
707
+
708
+ The sandbox is terminated whether the tier passed or not.
709
+ """
710
+ if environment is None:
711
+ raise EnvironmentsError(
712
+ CAPABILITY_UNSUPPORTED,
713
+ "A Modal smoke test needs the version's spec: the core tier "
714
+ "asks for the Python version it declared and the packages its "
715
+ "lock pinned, and an artifact carries neither",
716
+ detail={"variant": self.variant},
717
+ )
718
+ from ...modal_sandbox import ModalSandbox
719
+ from ...models import SandboxConfig
720
+ from ..conformance import expected_packages, run_core_tier
721
+
722
+ sdk = self._modal_sdk()
723
+ sandbox = ModalSandbox(
724
+ config=SandboxConfig(name=f"smoke-{artifact.provider_artifact_id}"),
725
+ app_name=f"dl-{environment.metadata.name}",
726
+ image_id=artifact.provider_artifact_id,
727
+ client=self._client(sdk),
728
+ )
729
+ self._log(f"Launching {artifact.provider_artifact_id} to smoke-test it")
730
+ try:
731
+ sandbox.start()
732
+ return run_core_tier(
733
+ sandbox,
734
+ python_version=environment.spec.language.version,
735
+ expected_packages=expected_packages(environment, lock_text or ""),
736
+ secret_values=tuple(secret_values),
737
+ restart=lambda: self._restart(sandbox),
738
+ )
739
+ except EnvironmentsError:
740
+ raise
741
+ except Exception as error:
742
+ raise self._provider_error("smoke-test the image", error) from error
743
+ finally:
744
+ try:
745
+ sandbox.stop()
746
+ except Exception as error:
747
+ self._log(f"The smoke-test sandbox could not be stopped: {error}")
748
+
749
+ @staticmethod
750
+ def _restart(sandbox: Any) -> None:
751
+ """Check 7's restart, as Modal can do it: a new sandbox from the same image."""
752
+ sandbox.stop()
753
+ sandbox.start()
754
+
668
755
  def delete(self, artifact: ArtifactReference) -> None:
669
756
  """Delete the image, and every intermediate layer this build recorded (E2-05, E2-09).
670
757
 
@@ -49,7 +49,16 @@ from .errors import (
49
49
  SCAN_BLOCKED,
50
50
  EnvironmentsError,
51
51
  )
52
- from .policy import DEFAULT_POLICY, PolicyDecision, ScanPolicy, decide, findings_of
52
+ from .policy import (
53
+ DEFAULT_POLICY,
54
+ UNRESTRICTED_POLICY,
55
+ EnvironmentsPolicy,
56
+ PolicyDecision,
57
+ ScanPolicy,
58
+ decide,
59
+ findings_of,
60
+ refuse_unlicensed,
61
+ )
53
62
 
54
63
  logger = logging.getLogger(__name__)
55
64
 
@@ -116,47 +125,72 @@ _SPDX_LICENSE_KEYS = ("licenseConcluded", "licenseDeclared")
116
125
  _SPDX_UNKNOWN = frozenset({"NOASSERTION", "NONE", ""})
117
126
 
118
127
 
119
- def _spdx_licenses(document: Mapping[str, Any]) -> set[str]:
120
- """What an SPDX document names, which is what BuildKit's `attest:sbom=` writes.
128
+ def _spdx_license_pairs(document: Mapping[str, Any]) -> list[tuple[str, str]]:
129
+ """Every SPDX package with a licence, as `(package, licence)`.
121
130
 
122
131
  `licenseConcluded` is what the tool decided and `licenseDeclared` what the
123
132
  package claimed, so the concluded one is read first and the declared one
124
133
  only when it said nothing.
125
134
  """
126
- found: set[str] = set()
135
+ found: list[tuple[str, str]] = []
127
136
  for package in document.get("packages") or ():
128
137
  if not isinstance(package, Mapping):
129
138
  continue
139
+ name = str(package.get("name") or "").strip()
130
140
  for key in _SPDX_LICENSE_KEYS:
131
141
  value = str(package.get(key) or "").strip()
132
142
  if value and value.upper() not in _SPDX_UNKNOWN:
133
- found.add(value)
143
+ # Kept even with no name: `licenses_of` reads only the licence
144
+ # half, and dropping an unnamed package's licence there would
145
+ # be exactly the silent behavior change this refactor must
146
+ # not make. `licenses_by_package`'s own callers decide what an
147
+ # empty package name means for them.
148
+ found.append((name, value))
134
149
  break
135
150
  return found
136
151
 
137
152
 
138
- def _cyclonedx_licenses(document: Mapping[str, Any]) -> set[str]:
139
- """What a CycloneDX document names, by id, by name, or as an expression."""
140
- found: set[str] = set()
153
+ def _cyclonedx_license_pairs(document: Mapping[str, Any]) -> list[tuple[str, str]]:
154
+ """Every CycloneDX component with a licence, as `(component, licence)`."""
155
+ found: list[tuple[str, str]] = []
141
156
  for component in document.get("components") or ():
142
157
  if not isinstance(component, Mapping):
143
158
  continue
159
+ name = str(component.get("name") or "").strip()
144
160
  for entry in component.get("licenses") or ():
145
161
  if not isinstance(entry, Mapping):
146
162
  continue
147
163
  licence = entry.get("license")
164
+ value = ""
148
165
  if isinstance(licence, Mapping):
149
166
  for key in _CYCLONEDX_LICENSE_KEYS:
150
167
  value = str(licence.get(key) or "").strip()
151
168
  if value:
152
- found.add(value)
153
169
  break
154
- expression = str(entry.get("expression") or "").strip()
155
- if expression:
156
- found.add(expression)
170
+ if not value:
171
+ value = str(entry.get("expression") or "").strip()
172
+ if value:
173
+ # Kept even with no component name; see the SPDX reader's own
174
+ # note above.
175
+ found.append((name, value))
157
176
  return found
158
177
 
159
178
 
179
+ def licenses_by_package(document: Any) -> list[tuple[str, str]]:
180
+ """Every `(package, licence)` an SBOM names, package first (E3-06).
181
+
182
+ The same two shapes `licenses_of` reads, kept attributed rather than
183
+ flattened: a policy that denies a licence has to name the package that
184
+ carries it, which `licenses_of`'s own deduplicated set of licence strings
185
+ alone cannot answer. Insertion order, not sorted — the order the SBOM's
186
+ own packages/components came in, which is what a refusal naming "the
187
+ first one" should mean deterministically for the same document.
188
+ """
189
+ if not isinstance(document, Mapping):
190
+ return []
191
+ return _spdx_license_pairs(document) + _cyclonedx_license_pairs(document)
192
+
193
+
160
194
  def licenses_of(document: Any) -> list[str]:
161
195
  """Every licence an SBOM names, deduplicated and sorted.
162
196
 
@@ -173,7 +207,7 @@ def licenses_of(document: Any) -> list[str]:
173
207
  """
174
208
  if not isinstance(document, Mapping):
175
209
  return []
176
- return sorted(_spdx_licenses(document) | _cyclonedx_licenses(document))
210
+ return sorted({licence for _package, licence in licenses_by_package(document)})
177
211
 
178
212
 
179
213
  @dataclass(frozen=True)
@@ -589,12 +623,21 @@ class Attestor:
589
623
  sbom_ref: str = "",
590
624
  provenance_ref: str = "",
591
625
  sbom: Any = None,
626
+ environments_policy: EnvironmentsPolicy = UNRESTRICTED_POLICY,
592
627
  ) -> AttestationResult:
593
628
  """Scan, then sign: the order the Operator's check depends on (D-11).
594
629
 
595
630
  `sbom`, when the caller has the document, is read for the licences a
596
631
  publication carries; the registry's scanner reports vulnerabilities
597
632
  and never licences, so there is nowhere else they come from.
633
+
634
+ **Refused before signing, never after (E3-06).** A licence an
635
+ organization's own policy denies is checked against the same `sbom`,
636
+ naming the package that carries it, and raised before `sign` runs —
637
+ an artifact whose licence policy denies it is never signed, the same
638
+ as one whose scan blocks it: a signature is Datalayer's word that an
639
+ artifact may run, and this is the second of the two words that go
640
+ into it.
598
641
  """
599
642
  self.can_sign()
600
643
  decision = self.scan(repository=repository, digest=digest)
@@ -609,6 +652,8 @@ class Attestor:
609
652
  "blocking": [finding.id for finding in decision.blocking],
610
653
  },
611
654
  )
655
+ pairs = licenses_by_package(sbom)
656
+ refuse_unlicensed(pairs, environments_policy)
612
657
  signature, signed_now = self.sign(registry=registry, repository=repository, digest=digest)
613
658
  return AttestationResult(
614
659
  decision=decision,
@@ -619,7 +664,7 @@ class Attestor:
619
664
  if size_bytes is not None
620
665
  else self.size_of(repository=repository, digest=digest),
621
666
  signed_now=signed_now,
622
- licenses=tuple(licenses_of(sbom)),
667
+ licenses=tuple(sorted({licence for _package, licence in pairs})),
623
668
  )
624
669
 
625
670
  def size_of(self, *, repository: str, digest: str) -> int | None:
@@ -665,8 +710,10 @@ def attest_artifact(
665
710
  artifact: Any,
666
711
  credential: Any = None,
667
712
  policy: ScanPolicy = DEFAULT_POLICY,
713
+ environments_policy: EnvironmentsPolicy = UNRESTRICTED_POLICY,
668
714
  log: Callable[[str], None] | None = None,
669
715
  size_bytes: int | None = None,
716
+ sbom: Any = None,
670
717
  attestor: Attestor | None = None,
671
718
  ) -> dict[str, Any]:
672
719
  """The `attest` seam of `EnvironmentBuildWorkflow` (E1-08, E1-09).
@@ -674,6 +721,13 @@ def attest_artifact(
674
721
  Takes the artifact the builder recorded and the build's credential, and
675
722
  answers the mapping the workflow stores: the scan's decision, the
676
723
  signature, the SBOM and provenance references, and the size.
724
+
725
+ `environments_policy` is the caller's organization's own narrowing of
726
+ what licence a build may carry (E3-06); `sbom`, when the caller has the
727
+ document, is what it is checked against — nobody fetches one here. A
728
+ caller with neither passes nothing through, and nothing is refused: the
729
+ platform default is unrestricted on licences, the same as every other
730
+ dimension of this policy until an organization writes one.
677
731
  """
678
732
  variant = str(getattr(artifact, "variant", "") or "")
679
733
  reference = str(getattr(artifact, "immutable_reference", "") or "")
@@ -724,6 +778,8 @@ def attest_artifact(
724
778
  repository=repository,
725
779
  digest=digest,
726
780
  size_bytes=size_bytes,
781
+ sbom=sbom,
782
+ environments_policy=environments_policy,
727
783
  ).body()
728
784
 
729
785
 
@@ -457,6 +457,36 @@ def _secrets(
457
457
  )
458
458
 
459
459
 
460
+ def expected_packages(environment: Any, lock_text: str) -> dict[str, str]:
461
+ """Each top-level distribution the spec names, pinned to what its lock resolved.
462
+
463
+ What Appendix B check 5 is given. Never every package the lock pins — most
464
+ of a real lock is transitive, and check 5 tries to *import* each name it is
465
+ handed; a build tool or a C-library-only wheel that was never meant to be
466
+ imported directly would fail a check with nothing wrong to report. The
467
+ spec's own `packages.python.dependencies` is what a person declared
468
+ wanting, so it is what disagreeing across variants (E2-08) means something
469
+ about.
470
+
471
+ Here rather than in the durable worker, which had the only copy, so that a
472
+ builder running its own smoke test asks the same question of its own
473
+ artifact (E2-03/04/05).
474
+ """
475
+ from packaging.requirements import InvalidRequirement, Requirement
476
+ from packaging.utils import canonicalize_name
477
+
478
+ from .resolve import locked_versions
479
+
480
+ pinned = locked_versions(lock_text) if lock_text else {}
481
+ names: list[str] = []
482
+ for text in environment.spec.packages.python.dependencies:
483
+ try:
484
+ names.append(canonicalize_name(Requirement(text).name))
485
+ except InvalidRequirement:
486
+ continue
487
+ return {name: pinned[name] for name in names if name in pinned}
488
+
489
+
460
490
  def run_core_tier(
461
491
  sandbox: Sandbox,
462
492
  *,
@@ -551,9 +581,7 @@ def _gpu(sandbox: Sandbox, requested: bool, cuda: str | None, timeout: float | N
551
581
  # version its spec describes. A version that asked for none never
552
582
  # reaches here (the trivial pass above), so the extended tier still
553
583
  # gates nothing for a CPU version.
554
- return _result(
555
- 11, not problems, gating=True, detail="; ".join(problems) or None, actual=answer
556
- )
584
+ return _result(11, not problems, gating=True, detail="; ".join(problems) or None, actual=answer)
557
585
 
558
586
 
559
587
  def _throughput(