code-sandboxes 1.9.1__tar.gz → 1.9.3__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 (262) hide show
  1. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/CHANGELOG.md +25 -0
  2. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/PKG-INFO +1 -1
  3. code_sandboxes-1.9.3/build.log +5 -0
  4. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/__version__.py +1 -1
  5. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/datalayer.py +45 -15
  6. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/spec.py +24 -0
  7. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_datalayer_builder.py +47 -13
  8. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_spec.py +29 -1
  9. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/build.yml +0 -0
  10. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/environments-live.yml +0 -0
  11. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/py-code-style.yml +0 -0
  12. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/py-tests.yml +0 -0
  13. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/py-typing.yml +0 -0
  14. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.github/workflows/reusable-python.yml +0 -0
  15. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.gitignore +0 -0
  16. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.licenserc.yaml +0 -0
  17. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.pre-commit-config.yaml +0 -0
  18. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/.vscode/settings.json +0 -0
  19. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/Dockerfile +0 -0
  20. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/LICENSE +0 -0
  21. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/Makefile +0 -0
  22. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/README.md +0 -0
  23. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/__init__.py +0 -0
  24. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/base.py +0 -0
  25. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/bridge_mount.py +0 -0
  26. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/builds.py +0 -0
  27. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/cli.py +0 -0
  28. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/client.py +0 -0
  29. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/cloudflare_sandbox.py +0 -0
  30. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/commands.py +0 -0
  31. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/console.py +0 -0
  32. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/contents.py +0 -0
  33. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/coreweave_sandbox.py +0 -0
  34. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/datalayer_sandbox.py +0 -0
  35. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/daytona_sandbox.py +0 -0
  36. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/docker_sandbox.py +0 -0
  37. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/e2b_sandbox.py +0 -0
  38. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/__init__.py +0 -0
  39. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/__main__.py +0 -0
  40. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/accounts.py +0 -0
  41. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/__init__.py +0 -0
  42. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/daytona.py +0 -0
  43. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/e2b.py +0 -0
  44. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/managed.py +0 -0
  45. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/adapters/modal.py +0 -0
  46. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/attest.py +0 -0
  47. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/bases.py +0 -0
  48. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/breaker.py +0 -0
  49. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/build_secrets.py +0 -0
  50. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/builders.py +0 -0
  51. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/canonical.py +0 -0
  52. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/conformance.py +0 -0
  53. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
  54. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
  55. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
  56. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/contract.py +0 -0
  57. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/doctor/__init__.py +0 -0
  58. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/doctor/build.py +0 -0
  59. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
  60. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/doctor/report.schema.json +0 -0
  61. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/errors.py +0 -0
  62. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/files.py +0 -0
  63. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/image_import.py +0 -0
  64. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/lifecycle.py +0 -0
  65. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/policy.py +0 -0
  66. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/redact.py +0 -0
  67. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/resolve.py +0 -0
  68. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/environments/schema.py +0 -0
  69. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/eval_sandbox.py +0 -0
  70. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/exceptions.py +0 -0
  71. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/filesystem.py +0 -0
  72. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/google_colab.py +0 -0
  73. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/google_colab_sandbox.py +0 -0
  74. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/interfaces.py +0 -0
  75. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/jupyter_ingress.py +0 -0
  76. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/jupyter_server_sandbox.py +0 -0
  77. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/kaggle.py +0 -0
  78. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/kaggle_execute.py +0 -0
  79. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/kaggle_live.py +0 -0
  80. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/kaggle_sandbox.py +0 -0
  81. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/lifecycle.py +0 -0
  82. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/manage.py +0 -0
  83. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/modal_sandbox.py +0 -0
  84. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/models.py +0 -0
  85. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/monty_sandbox.py +0 -0
  86. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/provider_ingress.py +0 -0
  87. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/code_sandboxes/providers.py +0 -0
  88. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docker/Dockerfile +0 -0
  89. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/.gitignore +0 -0
  90. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/LICENSE +0 -0
  91. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/Makefile +0 -0
  92. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/README.md +0 -0
  93. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/babel.config.js +0 -0
  94. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/api-reference/_category_.yml +0 -0
  95. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/api-reference/index.mdx +0 -0
  96. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/cli/_category_.yml +0 -0
  97. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/cli/index.mdx +0 -0
  98. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/contribute/_category_.yml +0 -0
  99. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/contribute/index.mdx +0 -0
  100. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/environments/_category_.yml +0 -0
  101. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/environments/contract.mdx +0 -0
  102. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/environments/index.mdx +0 -0
  103. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/environments/specification.mdx +0 -0
  104. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/environments/spike-2026-09.mdx +0 -0
  105. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/examples/_category_.yml +0 -0
  106. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/examples/index.mdx +0 -0
  107. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/guide/_category_.yml +0 -0
  108. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/guide/index.mdx +0 -0
  109. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/index.mdx +0 -0
  110. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/install/_category_.yml +0 -0
  111. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/install/index.mdx +0 -0
  112. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/provider-ingress.mdx +0 -0
  113. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/_category_.yml +0 -0
  114. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/cloudflare.mdx +0 -0
  115. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/coreweave.mdx +0 -0
  116. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/datalayer.mdx +0 -0
  117. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/daytona.mdx +0 -0
  118. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/docker.mdx +0 -0
  119. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/e2b.mdx +0 -0
  120. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/eval.mdx +0 -0
  121. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/google-colab.mdx +0 -0
  122. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/index.mdx +0 -0
  123. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/jupyter-server.mdx +0 -0
  124. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/kaggle.mdx +0 -0
  125. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/modal.mdx +0 -0
  126. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docs/providers/monty.mdx +0 -0
  127. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/docusaurus.config.js +0 -0
  128. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/package.json +0 -0
  129. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/sidebars.js +0 -0
  130. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow1.js +0 -0
  131. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow1.module.css +0 -0
  132. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow2.js +0 -0
  133. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow2.module.css +0 -0
  134. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow3.js +0 -0
  135. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/components/HomepageRow3.module.css +0 -0
  136. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/css/custom.css +0 -0
  137. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/pages/index.module.css +0 -0
  138. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/pages/markdown-page.md +0 -0
  139. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/theme/CustomDocItem.tsx +0 -0
  140. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/src/theme/IconsTitle.tsx +0 -0
  141. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/bricks.svg +0 -0
  142. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/building-construction.svg +0 -0
  143. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/cloud-sun.svg +0 -0
  144. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/cloud.svg +0 -0
  145. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/collaboration.svg +0 -0
  146. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/copy-left.svg +0 -0
  147. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/logo.png +0 -0
  148. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/logo.svg +0 -0
  149. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/personas/business.svg +0 -0
  150. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
  151. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/personas/devops.svg +0 -0
  152. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/datalayer/project/project-line.svg +0 -0
  153. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/favicon.ico +0 -0
  154. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/jupyter.svg +0 -0
  155. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/open-source.svg +0 -0
  156. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/react-js.svg +0 -0
  157. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/react-js2.svg +0 -0
  158. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/ringed-planet.svg +0 -0
  159. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/robot.svg +0 -0
  160. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/rocket.svg +0 -0
  161. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/shield.svg +0 -0
  162. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/sparkles.svg +0 -0
  163. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/streamlit.gif +0 -0
  164. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/super-hero.svg +0 -0
  165. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/target.svg +0 -0
  166. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/docs/static/img/thumbs-up.svg +0 -0
  167. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/README.md +0 -0
  168. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/Makefile +0 -0
  169. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/cloudflare_sandbox_example.py +0 -0
  170. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/coreweave_sandbox_example.py +0 -0
  171. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/datalayer_sandbox_example.py +0 -0
  172. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/daytona_sandbox_example.py +0 -0
  173. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/docker_sandbox_example.py +0 -0
  174. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/e2b_sandbox_example.py +0 -0
  175. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/eval_sandbox_example.py +0 -0
  176. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/google_colab_sandbox_example.py +0 -0
  177. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/jupyter_server_sandbox_example.py +0 -0
  178. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/kaggle_sandbox_example.py +0 -0
  179. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/modal_sandbox_example.py +0 -0
  180. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/exec/monty_sandbox_example.py +0 -0
  181. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/Makefile +0 -0
  182. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/cloudflare_sandbox_example.py +0 -0
  183. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/coreweave_sandbox_example.py +0 -0
  184. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/datalayer_sandbox_example.py +0 -0
  185. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/daytona_sandbox_example.py +0 -0
  186. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/docker_sandbox_example.py +0 -0
  187. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/e2b_sandbox_example.py +0 -0
  188. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/eval_sandbox_example.py +0 -0
  189. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/google_colab_sandbox_example.py +0 -0
  190. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/jupyter_server_sandbox_example.py +0 -0
  191. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/kaggle_sandbox_example.py +0 -0
  192. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/modal_sandbox_example.py +0 -0
  193. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/examples/repl/monty_sandbox_example.py +0 -0
  194. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/pyproject.toml +0 -0
  195. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/schemas/environment-v1alpha1.json +0 -0
  196. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/__init__.py +0 -0
  197. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/conftest.py +0 -0
  198. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
  199. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_bridge_mount.py +0 -0
  200. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_builds.py +0 -0
  201. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_cli_exec.py +0 -0
  202. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_cli_repl.py +0 -0
  203. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_client.py +0 -0
  204. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_client_contents.py +0 -0
  205. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_client_files.py +0 -0
  206. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_client_outcome_error_output.py +0 -0
  207. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_cloudflare.py +0 -0
  208. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_commands_envelope.py +0 -0
  209. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_console.py +0 -0
  210. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_contents_contract.py +0 -0
  211. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_contents_live_matrix.py +0 -0
  212. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_coreweave.py +0 -0
  213. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_datalayer_sandbox.py +0 -0
  214. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_daytona.py +0 -0
  215. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_e2b.py +0 -0
  216. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_accounts.py +0 -0
  217. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_attest.py +0 -0
  218. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_bases.py +0 -0
  219. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_breaker.py +0 -0
  220. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_build_secrets.py +0 -0
  221. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_builders.py +0 -0
  222. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_canonical.py +0 -0
  223. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_conformance.py +0 -0
  224. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_constraints.py +0 -0
  225. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_contract.py +0 -0
  226. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_cross_variant_report.py +0 -0
  227. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_daytona_builder.py +0 -0
  228. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_doctor.py +0 -0
  229. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_e2b_builder.py +0 -0
  230. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_errors.py +0 -0
  231. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_image_import.py +0 -0
  232. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_lifecycle.py +0 -0
  233. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_live_matrix.py +0 -0
  234. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_managed_builders.py +0 -0
  235. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_modal_builder.py +0 -0
  236. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_redact.py +0 -0
  237. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_environment_resolve.py +0 -0
  238. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_eval.py +0 -0
  239. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
  240. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_factory.py +0 -0
  241. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_google_colab.py +0 -0
  242. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_integration.py +0 -0
  243. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_jupyter_ingress.py +0 -0
  244. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_jupyter_server.py +0 -0
  245. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_jupyter_server_streaming.py +0 -0
  246. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_kaggle.py +0 -0
  247. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_kaggle_execute.py +0 -0
  248. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_kaggle_live.py +0 -0
  249. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_kaggle_session.py +0 -0
  250. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_kernel_client_compatibility.py +0 -0
  251. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_lifecycle_vocabulary.py +0 -0
  252. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_manage.py +0 -0
  253. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_manage_datalayer.py +0 -0
  254. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_modal_google_colab_sandbox.py +0 -0
  255. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_modal_session.py +0 -0
  256. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_models.py +0 -0
  257. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_output_line_fidelity.py +0 -0
  258. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_provider_catalog_names.py +0 -0
  259. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_provider_ingress.py +0 -0
  260. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_providers.py +0 -0
  261. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_sandboxes.py +0 -0
  262. {code_sandboxes-1.9.1 → code_sandboxes-1.9.3}/tests/test_the_kill_relaunch_drill.py +0 -0
@@ -8,6 +8,31 @@
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 1.9.3
12
+
13
+ - **A build secret is mounted only on the `postInstall` commands that name it**
14
+ (`environments.spec.command_names_secret`, `environments.adapters.datalayer`;
15
+ PLAN_ENV.md E3-05). Every declared secret used to be mounted on every
16
+ `postInstall` `RUN`. A command now gets a secret's mount only when the
17
+ secret's name is in it as a whole word (`$NAME`, `${NAME}`, `--key-env NAME`,
18
+ `/run/secrets/NAME`). A declared secret that no command names is a
19
+ `DL_ENV_SPEC_INVALID` finding on `spec.buildSecrets[i]`, rather than a build
20
+ that runs with it empty.
21
+
22
+ ## 1.9.2
23
+
24
+ - **A real ECR repository name is lowercase; a real owner uid is not**
25
+ (`environments.adapters.datalayer.owner_repository`, new
26
+ `owner_cache_repository`; PLAN_ENV.md E1-07). Found live, 2026-09-14, the
27
+ first real Datalayer build ever run for a real account's own uid rather
28
+ than a lowercase test fixture: `DescribeRepositories` refused outright,
29
+ "Invalid parameter at 'repositoryName'" — this project's own uids are
30
+ ULIDs, conventionally uppercase, and ECR repository names match
31
+ `[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*` per path segment. Both the owner's own
32
+ repository and the owner's cache repository are lowered at the one place
33
+ each is built, so every caller (create, inspect, exists, delete, the
34
+ cache import/export) stays consistent with itself.
35
+
11
36
  ## 1.9.1
12
37
 
13
38
  - **mTLS to the build pool's `buildkitd`** (`environments.adapters.datalayer.Builder`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code_sandboxes
3
- Version: 1.9.1
3
+ Version: 1.9.3
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
@@ -0,0 +1,5 @@
1
+ * Creating isolated environment: venv+pip...
2
+ * Installing packages in isolated environment:
3
+ - hatchling~=1.21
4
+ * Getting build dependencies for sdist...
5
+ * Building sdist...
@@ -3,4 +3,4 @@
3
3
 
4
4
  """Code Sandboxes."""
5
5
 
6
- __version__ = "1.9.1"
6
+ __version__ = "1.9.3"
@@ -69,9 +69,14 @@ from ..errors import (
69
69
  )
70
70
  from ..files import files_step
71
71
  from ..resolve import WHEELHOUSE_IMAGE_PATH, WHEELHOUSE_PATH, apt_pins_in, locked_versions
72
- from ..spec import BuildSecret, Environment
72
+ from ..spec import BuildSecret, Environment, command_names_secret
73
73
 
74
- __all__ = ["ECR_ENVIRONMENT_PREFIX", "Builder", "owner_repository"]
74
+ __all__ = [
75
+ "ECR_ENVIRONMENT_PREFIX",
76
+ "Builder",
77
+ "owner_cache_repository",
78
+ "owner_repository",
79
+ ]
75
80
 
76
81
  #: Where an owner's environments live in ECR (D-10).
77
82
  ECR_ENVIRONMENT_PREFIX = "environments/u/"
@@ -89,10 +94,32 @@ DEFAULT_MAX_ARTIFACT_BYTES = 20 * 1024**3
89
94
 
90
95
 
91
96
  def owner_repository(owner_uid: str, environment_name: str) -> str:
92
- """The owner's repository for one environment: ``environments/u/<owner>/<name>``."""
97
+ """The owner's repository for one environment: ``environments/u/<owner>/<name>``.
98
+
99
+ Lowercased: an ECR repository name matches
100
+ ``[a-z0-9]+((\\.|_|__|-+)[a-z0-9]+)*`` per path segment, and this
101
+ project's own uids are ULIDs, conventionally uppercase — found live,
102
+ 2026-09-14, the first real build ever run for a real account's own
103
+ uid rather than a lowercase test fixture: ``DescribeRepositories``
104
+ refused it outright, "Invalid parameter at 'repositoryName'". Lowering
105
+ both segments here, the one place this reference is built, keeps every
106
+ caller (create, inspect, exists, delete) consistent with itself without
107
+ each needing to remember to.
108
+ """
93
109
  if not owner_uid or not environment_name:
94
110
  raise ValueError("an owner and an environment name make the repository")
95
- return f"{ECR_ENVIRONMENT_PREFIX}{owner_uid}/{environment_name}"
111
+ return f"{ECR_ENVIRONMENT_PREFIX}{owner_uid}/{environment_name}".lower()
112
+
113
+
114
+ def owner_cache_repository(owner_uid: str) -> str:
115
+ """The owner's own BuildKit cache repository: ``environments/cache/u/<owner>``.
116
+
117
+ Lowercased for the same reason ``owner_repository`` is (a ULID, not a
118
+ lowercase test fixture, is what a real build actually names).
119
+ """
120
+ if not owner_uid:
121
+ raise ValueError("an owner makes the cache repository")
122
+ return f"environments/cache/u/{owner_uid}".lower()
96
123
 
97
124
 
98
125
  def _secret_mount(secret: BuildSecret) -> str:
@@ -320,15 +347,14 @@ class Builder:
320
347
  "/opt/datalayer/lock.txt",
321
348
  ]
322
349
  )
323
- # A build secret is mounted on the postInstall step and nowhere else
324
- # (§4.1, D-11): never an `ARG` or `ENV`, which bakes a value into the
325
- # image's history, and never the package-install or files steps,
326
- # which no secret is declared for. `--mount=type=secret` is a
327
- # BuildKit mount: the value is available to the one `RUN`'s shell and
328
- # never written to a layer.
350
+ # A build secret is mounted on the postInstall commands that name it
351
+ # and nowhere else (§4.1, D-11): never an `ARG` or `ENV`, which bakes a
352
+ # value into the image's history, never the package-install or files
353
+ # steps, and never a postInstall command that does not read it.
354
+ # `--mount=type=secret` is a BuildKit mount: the value is available to
355
+ # the one `RUN`'s shell and never written to a layer.
329
356
  wanted = set(request.build_secret_ids)
330
357
  secrets = [secret for secret in spec.build_secrets if secret.id in wanted]
331
- secret_mounts = " ".join(_secret_mount(secret) for secret in secrets)
332
358
  baked = files_step(request.environment, variant=self.variant)
333
359
  if baked:
334
360
  lines.append("USER 1000:100")
@@ -338,11 +364,15 @@ class Builder:
338
364
  if spec.commands.post_install:
339
365
  lines.append("USER 1000:100")
340
366
  lines.append("WORKDIR /home/datalayer/content")
341
- prefix = "RUN --network=none " + (f"{secret_mounts} " if secret_mounts else "")
342
367
  for command in spec.commands.post_install:
343
368
  # No network: a command that fetches something makes an
344
369
  # artifact whose contents depend on the day it was built.
345
- lines.append(f"{prefix}{command}")
370
+ mounts = "".join(
371
+ f"{_secret_mount(secret)} "
372
+ for secret in secrets
373
+ if command_names_secret(command, secret)
374
+ )
375
+ lines.append(f"RUN --network=none {mounts}{command}")
346
376
  lines.extend(
347
377
  [
348
378
  "USER 1000:100",
@@ -374,7 +404,7 @@ class Builder:
374
404
  repository = owner_repository(request.owner_uid, request.environment.metadata.name)
375
405
  registry = self._registry_host()
376
406
  self._ensure_repository(repository)
377
- self._ensure_cache_repository(f"environments/cache/u/{request.owner_uid}")
407
+ self._ensure_cache_repository(owner_cache_repository(request.owner_uid))
378
408
  tag = f"v{request.version}-{request.build_uid}"
379
409
  reference = f"{registry}/{repository}:{tag}"
380
410
  with tempfile.TemporaryDirectory(prefix="dl-build-") as directory:
@@ -637,7 +667,7 @@ class Builder:
637
667
  def _cache_options(self, request: BuildRequest) -> list[str]:
638
668
  """The owner's cache, imported and exported: never another owner's (D-12)."""
639
669
  registry = self._registry_host()
640
- cache = f"{registry}/environments/cache/u/{request.owner_uid}:{self.variant}"
670
+ cache = f"{registry}/{owner_cache_repository(request.owner_uid)}:{self.variant}"
641
671
  return [
642
672
  "--import-cache",
643
673
  f"type=registry,ref={cache}",
@@ -83,6 +83,7 @@ __all__ = [
83
83
  "VariantSet",
84
84
  "VersionStatus",
85
85
  "assert_publishable",
86
+ "command_names_secret",
86
87
  "parse_environment",
87
88
  "parse_requirements_txt",
88
89
  "publication_findings",
@@ -200,6 +201,18 @@ class BuildSecret(_Model):
200
201
  name: str = Field(pattern=r"^[A-Za-z0-9_.-]+$")
201
202
 
202
203
 
204
+ def command_names_secret(command: str, secret: BuildSecret) -> bool:
205
+ """Whether one ``postInstall`` command names ``secret``, so its step gets the mount (E3-05).
206
+
207
+ A command names a secret when the secret's name is in it as a whole word:
208
+ ``$PIP_TOKEN``, ``${PIP_TOKEN}``, ``--key-env PIP_TOKEN`` or
209
+ ``/run/secrets/netrc``. A secret is mounted on exactly the steps that name
210
+ it (§4.1), so a command that reads none holds none.
211
+ """
212
+ word = rf"(?<![A-Za-z0-9_.-]){re.escape(secret.name)}(?![A-Za-z0-9_.-])"
213
+ return re.search(word, command) is not None
214
+
215
+
203
216
  class Accelerator(_Model):
204
217
  type: str
205
218
  count: int = Field(default=1, ge=1)
@@ -640,6 +653,17 @@ def spec_findings(
640
653
  values = [getattr(secret, attribute) for secret in spec.build_secrets]
641
654
  if len(values) != len(set(values)):
642
655
  findings.append(SpecFinding("spec.buildSecrets", f"a secret {label} is listed twice"))
656
+ for index, secret in enumerate(spec.build_secrets):
657
+ # A secret no command names would be mounted nowhere: the build would
658
+ # run with it empty and fail far from the cause.
659
+ if not any(command_names_secret(command, secret) for command in spec.commands.post_install):
660
+ findings.append(
661
+ SpecFinding(
662
+ f"spec.buildSecrets[{index}]",
663
+ f"`{secret.name}` is named by no `postInstall` command, and a secret "
664
+ "is mounted only on the commands that name it",
665
+ )
666
+ )
643
667
 
644
668
  resources = spec.resources
645
669
  if resources.size_class not in SIZE_CLASSES:
@@ -19,7 +19,11 @@ from pathlib import Path
19
19
 
20
20
  import pytest
21
21
 
22
- from code_sandboxes.environments.adapters.datalayer import Builder, owner_repository
22
+ from code_sandboxes.environments.adapters.datalayer import (
23
+ Builder,
24
+ owner_cache_repository,
25
+ owner_repository,
26
+ )
23
27
  from code_sandboxes.environments.builders import ArtifactReference, BuildRequest
24
28
  from code_sandboxes.environments.errors import EnvironmentsError
25
29
  from code_sandboxes.environments.spec import parse_environment
@@ -91,6 +95,18 @@ class FakeEcr:
91
95
  return error
92
96
 
93
97
 
98
+ #: One build secret, named by one of two `postInstall` commands (E3-05).
99
+ A_SECRET = {
100
+ "buildSecrets": [{"id": "dlsec_01J9BUILDSECRET0000000000", "name": "PIP_TOKEN"}],
101
+ "commands": {
102
+ "postInstall": [
103
+ "python -c 'import geopandas'",
104
+ "python unpack_tiles.py --key-env PIP_TOKEN",
105
+ ]
106
+ },
107
+ }
108
+
109
+
94
110
  def a_request(**changes) -> BuildRequest:
95
111
  """The section 4.1 example as a build of version 3, attempt `bld-1`."""
96
112
  spec = {
@@ -232,21 +248,20 @@ class TestTheDockerfileItGenerates:
232
248
  # Never the loose list: that is the whole point of resolving once.
233
249
  assert "geopandas==1.1.1" not in dockerfile
234
250
 
235
- def test_a_build_secret_is_mounted_for_the_postinstall_step_alone(self) -> None:
236
- """Mounted on the `postInstall` `RUN` alone — never an `ARG`/`ENV`,
237
- which bakes a value into the image's history, and never the
238
- package-install or files steps, which name no secret (§4.1, D-11)."""
239
- request = a_request(
240
- spec={"buildSecrets": [{"id": "dlsec_01J9BUILDSECRET0000000000", "name": "PIP_TOKEN"}]},
241
- build_secret_ids=("dlsec_01J9BUILDSECRET0000000000",),
242
- )
251
+ def test_a_build_secret_is_mounted_for_the_command_that_names_it_alone(self) -> None:
252
+ """Mounted on the `postInstall` `RUN` that names it alone — never an
253
+ `ARG`/`ENV`, which bakes a value into the image's history, never the
254
+ package-install or files steps, and never a `postInstall` command
255
+ that does not read it (§4.1, D-11)."""
256
+ request = a_request(spec=A_SECRET, build_secret_ids=("dlsec_01J9BUILDSECRET0000000000",))
243
257
  dockerfile = a_builder().dockerfile(request)
244
258
  lines = dockerfile.splitlines()
245
259
  mount_lines = [line for line in lines if "--mount=type=secret" in line]
246
260
  assert mount_lines == [
247
261
  "RUN --network=none --mount=type=secret,id=dlsec_01J9BUILDSECRET0000000000,"
248
- "env=PIP_TOKEN python -c 'import geopandas'"
262
+ "env=PIP_TOKEN python unpack_tiles.py --key-env PIP_TOKEN"
249
263
  ]
264
+ assert "RUN --network=none python -c 'import geopandas'" in lines
250
265
  # Nowhere else in the Dockerfile: no `ARG`/`ENV` line, and no other
251
266
  # `RUN` mentions the id or the value's own name.
252
267
  assert not any(line.startswith(("ARG", "ENV")) and "PIP_TOKEN" in line for line in lines)
@@ -261,7 +276,8 @@ class TestTheDockerfileItGenerates:
261
276
  "name": "netrc",
262
277
  "mountAs": "file",
263
278
  }
264
- ]
279
+ ],
280
+ "commands": {"postInstall": ["python unpack_tiles.py --netrc /run/secrets/netrc"]},
265
281
  },
266
282
  build_secret_ids=("dlsec_01J9BUILDSECRET0000000000",),
267
283
  )
@@ -476,7 +492,7 @@ class TestBuildingAndPushing:
476
492
  return result
477
493
 
478
494
  request = a_request(
479
- spec={"buildSecrets": [{"id": "dlsec_01J9BUILDSECRET0000000000", "name": "PIP_TOKEN"}]},
495
+ spec=A_SECRET,
480
496
  build_secret_ids=("dlsec_01J9BUILDSECRET0000000000",),
481
497
  )
482
498
  buildctl = RecordingBuildctl()
@@ -497,7 +513,7 @@ class TestBuildingAndPushing:
497
513
  raise EnvironmentsError(BUILD_SECRET_UNAVAILABLE, "IAM is unreachable")
498
514
 
499
515
  request = a_request(
500
- spec={"buildSecrets": [{"id": "dlsec_01J9BUILDSECRET0000000000", "name": "PIP_TOKEN"}]},
516
+ spec=A_SECRET,
501
517
  build_secret_ids=("dlsec_01J9BUILDSECRET0000000000",),
502
518
  )
503
519
  buildctl = Buildctl()
@@ -565,6 +581,24 @@ class TestBuildingAndPushing:
565
581
  a_builder(run=Buildctl(), ecr=ecr).build(a_request())
566
582
  assert ecr.created == []
567
583
 
584
+ def test_a_real_uppercase_ulid_owner_still_names_a_valid_ecr_repository(self) -> None:
585
+ """Found live, 2026-09-14: the first real build for a real account's
586
+ own uid, not a lowercase test fixture. ECR repository names match
587
+ ``[a-z0-9]+((\\.|_|__|-+)[a-z0-9]+)*`` per segment; this project's own
588
+ uids are ULIDs, conventionally uppercase — `DescribeRepositories`
589
+ refused the request outright before this fix."""
590
+ ulid_owner = "01JV1VE1T5VG22Z05F6EFBMW8E"
591
+ ecr = FakeEcr()
592
+ a_builder(run=Buildctl(), ecr=ecr).build(a_request(owner_uid=ulid_owner))
593
+ names = [item["repositoryName"] for item in ecr.created]
594
+ assert names, "the build should have created at least the owner's own repository"
595
+ for name in names:
596
+ assert name == name.lower(), f"{name!r} is not a valid ECR repository name"
597
+ assert owner_repository(ulid_owner, "geospatial-analysis") == (
598
+ f"environments/u/{ulid_owner.lower()}/geospatial-analysis"
599
+ )
600
+ assert owner_cache_repository(ulid_owner) == f"environments/cache/u/{ulid_owner.lower()}"
601
+
568
602
  def test_the_credential_reaches_buildctl_and_not_the_workers_own_config(self) -> None:
569
603
  seen: dict[str, object] = {}
570
604
 
@@ -23,8 +23,10 @@ from code_sandboxes.environments.errors import EnvironmentsError
23
23
  from code_sandboxes.environments.schema import main as schema_main
24
24
  from code_sandboxes.environments.schema import schema_text
25
25
  from code_sandboxes.environments.spec import (
26
+ BuildSecret,
26
27
  Environment,
27
28
  assert_publishable,
29
+ command_names_secret,
28
30
  parse_environment,
29
31
  publication_findings,
30
32
  spec_digest,
@@ -72,10 +74,11 @@ spec:
72
74
  commands:
73
75
  postInstall:
74
76
  - "python -c 'import geopandas'"
77
+ - "python unpack_tiles.py --key-env TILES_LICENSE_KEY"
75
78
  buildSecrets:
76
79
  - id: dlsec_01J8ZK
77
80
  mountAs: env
78
- name: PIP_EXTRA_INDEX_TOKEN
81
+ name: TILES_LICENSE_KEY
79
82
  resources:
80
83
  accelerator: none
81
84
  hints:
@@ -195,6 +198,13 @@ UNSUPPORTED = "DL_ENV_CAPABILITY_UNSUPPORTED"
195
198
  ("spec.language.version", "3.9", "spec.language.version", INVALID),
196
199
  ("spec.build.source", "dockerfile", "spec.build.source", UNSUPPORTED),
197
200
  ("spec.packages.python.manager", "conda", "spec.packages.python.manager", UNSUPPORTED),
201
+ # E3-05: a secret no postInstall command names would be mounted nowhere.
202
+ (
203
+ "spec.commands.postInstall",
204
+ ["python -c 'import geopandas'"],
205
+ "spec.buildSecrets[0]",
206
+ INVALID,
207
+ ),
198
208
  (
199
209
  "spec.packages.python.dependencies.0",
200
210
  "geopandas>=>1",
@@ -526,6 +536,24 @@ def test_a_spec_is_digested_the_same_from_the_environment_or_the_spec() -> None:
526
536
  assert spec_digest(environment) == spec_digest(environment.spec)
527
537
 
528
538
 
539
+ @pytest.mark.parametrize(
540
+ ("command", "named"),
541
+ [
542
+ ("python unpack_tiles.py --key-env TOKEN", True),
543
+ ('curl -H "Authorization: $TOKEN" x', True),
544
+ ("echo ${TOKEN}", True),
545
+ ("cat /run/secrets/TOKEN", True),
546
+ ("echo $TOKEN_FILE", False),
547
+ ("echo $MY_TOKEN", False),
548
+ ("python -c 'import geopandas'", False),
549
+ ],
550
+ )
551
+ def test_a_command_names_a_secret_by_its_whole_name(command: str, named: bool) -> None:
552
+ """E3-05: the Datalayer builder mounts a secret on the commands that name it."""
553
+ secret = BuildSecret(id="dlsec_01J8ZK", name="TOKEN")
554
+ assert command_names_secret(command, secret) is named
555
+
556
+
529
557
  class TestPublicationFindings:
530
558
  """D-12: a version with any build secret can never be published to the Library."""
531
559
 
File without changes
File without changes
File without changes