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.
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/PKG-INFO +1 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/datalayer.py +19 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/daytona.py +114 -4
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/managed.py +18 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/modal.py +101 -14
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/attest.py +70 -14
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/conformance.py +31 -3
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/policy.py +133 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/spec.py +116 -8
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/manage.py +80 -6
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/modal_sandbox.py +14 -2
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_attest.py +85 -1
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_datalayer_builder.py +29 -4
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_daytona_builder.py +157 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_live_matrix.py +20 -2
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_managed_builders.py +30 -9
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_modal_builder.py +117 -7
- code_sandboxes-1.9.26/tests/test_environment_organization_policy.py +165 -0
- code_sandboxes-1.9.26/tests/test_environment_spec_organization_policy.py +198 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_manage.py +68 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_manage_datalayer.py +2 -3
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/build.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/environments-live.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-tests.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.gitignore +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.licenserc.yaml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.pre-commit-config.yaml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/.vscode/settings.json +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/CHANGELOG.md +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/Dockerfile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/LICENSE +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/Makefile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/README.md +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/base.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/client.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/commands.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/daytona_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/docker_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/e2b_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/__init__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/accounts.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/e2b.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/bases.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/breaker.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/build_secrets.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/builders.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
- {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
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/errors.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/image_import.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/resolve.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/resolve_conda.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/jupyter_server_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/kaggle_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docker/Dockerfile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/.gitignore +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/LICENSE +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/Makefile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/README.md +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/babel.config.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/specification.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/package.json +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/sidebars.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/README.md +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/pyproject.toml +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/schemas/environment-v1alpha1.json +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/__init__.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/conftest.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_bridge_mount.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_builds.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_commands_envelope.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_console.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_contents_live_matrix.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_daytona.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_e2b.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_accounts.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_bases.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_breaker.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_build_secrets.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_builders.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_conformance.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_contract.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_cross_variant_report.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_e2b_builder.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_errors.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_image_import.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_redact.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_resolve.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_resolve_conda.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_environment_spec.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_eval.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_factory.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_integration.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_server.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_modal_google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_modal_session.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_models.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_providers.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_sandboxes.py +0 -0
- {code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/tests/test_the_kill_relaunch_drill.py +0 -0
{code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/datalayer.py
RENAMED
|
@@ -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=
|
|
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}",
|
{code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/daytona.py
RENAMED
|
@@ -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
|
|
181
|
-
#:
|
|
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,
|
{code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/managed.py
RENAMED
|
@@ -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(
|
|
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:
|
{code_sandboxes-1.9.21 → code_sandboxes-1.9.26}/code_sandboxes/environments/adapters/modal.py
RENAMED
|
@@ -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
|
|
539
|
-
|
|
540
|
-
`
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
545
|
-
|
|
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
|
-
|
|
554
|
-
|
|
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
|
|
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
|
|
120
|
-
"""
|
|
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:
|
|
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
|
-
|
|
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
|
|
139
|
-
"""
|
|
140
|
-
found:
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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(
|
|
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(
|
|
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(
|