freesolo-flash-dev 0.2.29__tar.gz → 0.2.30__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.
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/bake-kernel-cache.yml +16 -29
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/PKG-INFO +1 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/__init__.py +8 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/auth.py +14 -4
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/envs.py +18 -23
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/routes/meta.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/pyproject.toml +2 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_billing_retry.py +6 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_help.py +31 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_client.py +3 -3
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_client_server_integration.py +2 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_publish.py +53 -59
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_server_api.py +52 -19
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_server_billing.py +11 -2
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_server_db.py +3 -1
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.dockerignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.env.example +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/auto-rebake.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/ci.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/main-source-guard.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/publish-dev.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/publish-image.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/publish.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/version-parity.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/worker-image.yml +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.gitignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/Dockerfile +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/Dockerfile.worker +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/LICENSE +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/README.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/build/kernel_cache/.gitignore +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/build/kernel_cache/.keep +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/Dockerfile.kernelcache +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/Dockerfile.kernelcache.relayer +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/bake_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/bake_pod_entry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docker/make_rp_handler.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-style/README.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-style/generate.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-style/index.html +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-style/preview.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-style/themed-errors.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/README.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/before-after-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/before-after-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/gallery-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/gallery-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/help-dark.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/cli-theme/help-light.png +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/docs/kernel-cache.md +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/_channel.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/_fileio.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/_logging.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/_update_check.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/catalog.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/commands.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/envpush.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/render.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cli/training_doc.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/client/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/client/config.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/client/http.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/client/runtime_secrets.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/client/specs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cost/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cost/analytical.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cost/facts.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cost/spec.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/cost/types.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/accounting.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/recipe.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/vram.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/_pkg.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/adapter.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/decoding.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/finalize.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/gpu_setup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/grpo.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/hf.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/kernel_warmup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/lora.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/packing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/attn.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/diagnostics.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/liger.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/loraplus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/perf/memory.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/rl.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/sft.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/engine/worker/wandb_log.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/envs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/envs/adapter.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/envs/base.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/envs/pull.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/envs/registry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/_auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/_http.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/_instance.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/_instance_bootstrap.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/_poll.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/allocator.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/base.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/api.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/gpus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/jobs/builders.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/pricing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/lambdalabs/train.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/realized.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/api.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/auth.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/cost.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/gpus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/jobs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/keys.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/preload.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/pricing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/slots.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/train/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/train/deps.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/providers/runpod/train/endpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/py.typed +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/runner/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/runner/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/runner/deploy.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/runner/lifecycle.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/schema/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/schema/fields.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/serve/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/serve/deploy.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/serve/export.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/serve/pricing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/__main__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/_deps.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/_locks.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/_runtime.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/app.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/billing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/billing_retry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/db.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/environment_registry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/reconcile.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/repo_cleanup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/routes/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/routes/envs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/routes/runs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/routes/serving.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/server/run_registry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/flash/spec.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/infisical-entrypoint.sh +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/scripts/build_dev_dist.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/_helpers/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/_helpers/runner.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/_helpers/specs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/conftest.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/fixtures/math_eval.jsonl +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/fixtures/math_train.jsonl +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/live/__init__.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/live/conftest.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/live/test_lambda_live.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/live/test_runpod_live.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_agent_flash_cli_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_algorithms.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_allocator.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_b200_rollout_opt.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_backend_jobspec_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cancel_remote.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_catalog_consistency.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_chalk_kernels.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_checkpoints.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_commands.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_errors.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_estimate.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_managed.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cli_render_theme.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_config_overrides.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_analytical.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_equation.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_estimate.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_hardware.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_models.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_cost_rewards.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_dev_channel.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_disk_gb.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_endpoint_name.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_delete.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_no_install.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_pull.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_push.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_env_rate_limit_resolve.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_envs_coverage.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_export.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_flash_mvp.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_flash_worker.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_flashinfer_cache_dirs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_github_urlopen_retry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_gpus.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_grpo_mask_aware.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_grpo_params.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_grpo_sleep_gate.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_idle_endpoint_reaper.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_internal_client.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_jobs.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_kernel_cache.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_kernel_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_kv_util.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_lambda_runner.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_logging.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_login_perms.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_managed_hf_repo.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_metrics_schema_agent_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_mig_guard.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_multiturn_rollout.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_oom_escalate_gpu.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_open_model_policy.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_orchestrator_flash.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_packing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_poll_helpers.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_preflight.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_provider_routing.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_provider_teardown_robustness.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_providers_symmetry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_realized_cost.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_repo_cleanup.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_resolve_params_b.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_resume_on_retry.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_runmgmt.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_runpod_api_delete.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_runpod_key_fingerprint.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_runpod_key_waterfall.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_runpod_slots.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_serve.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_serving_contract.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_sft_gc_off.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_spec_and_validation.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_thinking_config.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_update_check.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_verifiers.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_version.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_vl_warmstart_adapter_keys.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_vl_warmstart_recombine.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_vl_weight_sync.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_wandb_naming.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_warmstart_cross_repo.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_weight_cache.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_dryrun.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_hardexit.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_image.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_init_heartbeat.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_stack.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_worker_thinking.py +0 -0
- {freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/uv.lock +0 -0
{freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/.github/workflows/bake-kernel-cache.yml
RENAMED
|
@@ -11,8 +11,8 @@ name: flash worker kernel-cache (per-arch)
|
|
|
11
11
|
# warmup runs INSIDE it so the cache matches the image's pinned torch/triton/fla/liger toolchain.
|
|
12
12
|
#
|
|
13
13
|
# Secrets: RUNPOD_API_KEY (the warmup GPU pod) + HF_TOKEN (the temp dataset that ferries code + the
|
|
14
|
-
# cache artifact) are
|
|
15
|
-
#
|
|
14
|
+
# cache artifact) are native repo Actions secrets, synced from Infisical (prod:/flash-ci) by the
|
|
15
|
+
# github-ci-flash Secret Sync; the GHCR push uses the default GITHUB_TOKEN. Heavy + paid,
|
|
16
16
|
# but UNGATED: the fingerprint detection in auto-rebake.yml decides WHEN this runs (only on a real
|
|
17
17
|
# kernel-toolchain change), so there is no manual approval step. Invoked by hand (workflow_dispatch)
|
|
18
18
|
# OR automatically by auto-rebake.yml (workflow_call) when the kernel toolchain changed.
|
|
@@ -179,38 +179,25 @@ jobs:
|
|
|
179
179
|
if: steps.gate.outputs.run == 'true'
|
|
180
180
|
run: uv sync --extra server
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
# RUNPOD_API_KEY + HF_TOKEN come from Infisical (/flash, prod) -- mirrors infisical-entrypoint.sh;
|
|
189
|
-
# the CLI takes the project UUID directly (the secrets-action wants a slug). `env -u
|
|
190
|
-
# FLASH_CHALK_SPEC` drops any chalk-spec override living in /flash so the bake always warms the
|
|
191
|
-
# repo-default chalk (the value the fingerprint hashed) -- otherwise the image could be warmed with
|
|
192
|
-
# one chalk and labeled with another. BASE_IMAGE is digest-pinned so the warm runs inside the EXACT
|
|
193
|
-
# base the per-arch tag will sit on.
|
|
182
|
+
# RUNPOD_API_KEY (the warmup GPU pod) + HF_TOKEN (the temp dataset that ferries code/cache) are
|
|
183
|
+
# native repo Actions secrets, synced from Infisical (prod:/flash-ci) by the github-ci-flash
|
|
184
|
+
# Secret Sync. No Infisical CLI/login at job time -- Infisical stays the source of truth and
|
|
185
|
+
# rotations propagate via the sync. FLASH_CHALK_SPEC isn't synced, so bake_kernel_cache.py uses
|
|
186
|
+
# the repo-default DEFAULT_CHALK_SPEC (the value the fingerprint hashed) automatically. BASE_IMAGE
|
|
187
|
+
# is digest-pinned so the warm runs inside the EXACT base the per-arch tag will sit on.
|
|
194
188
|
- name: Warm kernels on a RunPod ${{ matrix.sm }} GPU -> build/kernel_cache
|
|
195
189
|
if: steps.gate.outputs.run == 'true'
|
|
196
190
|
env:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }}
|
|
191
|
+
RUNPOD_API_KEY: ${{ secrets.RUNPOD_API_KEY }}
|
|
192
|
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
|
200
193
|
BASE_IMAGE: ${{ steps.base.outputs.ref }}
|
|
201
194
|
run: |
|
|
202
|
-
|
|
203
|
-
--
|
|
204
|
-
--
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
uv run python docker/bake_kernel_cache.py \
|
|
209
|
-
--arch "${{ matrix.arch }}" --sm "${{ matrix.sm }}" \
|
|
210
|
-
--gpu-type-id "${{ matrix.gpu_type_id }}" \
|
|
211
|
-
--allowed-cuda "${{ matrix.allowed_cuda }}" \
|
|
212
|
-
--image "$BASE_IMAGE" \
|
|
213
|
-
--out build/kernel_cache
|
|
195
|
+
uv run python docker/bake_kernel_cache.py \
|
|
196
|
+
--arch "${{ matrix.arch }}" --sm "${{ matrix.sm }}" \
|
|
197
|
+
--gpu-type-id "${{ matrix.gpu_type_id }}" \
|
|
198
|
+
--allowed-cuda "${{ matrix.allowed_cuda }}" \
|
|
199
|
+
--image "$BASE_IMAGE" \
|
|
200
|
+
--out build/kernel_cache
|
|
214
201
|
|
|
215
202
|
- name: Free disk space
|
|
216
203
|
if: steps.gate.outputs.run == 'true'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: freesolo-flash-dev
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.30
|
|
4
4
|
Summary: Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/freesolo-co/flash
|
|
6
6
|
Project-URL: Repository, https://github.com/freesolo-co/flash
|
|
@@ -72,7 +72,11 @@ _HELP_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
72
72
|
(
|
|
73
73
|
"environments",
|
|
74
74
|
[
|
|
75
|
-
("env", "
|
|
75
|
+
("env setup", "scaffold a starter Freesolo environment"),
|
|
76
|
+
("env list", "list local environment sources"),
|
|
77
|
+
("env push", "upload a local environment"),
|
|
78
|
+
("env pull", "download a published environment or file"),
|
|
79
|
+
("env delete", "delete a published environment"),
|
|
76
80
|
],
|
|
77
81
|
),
|
|
78
82
|
(
|
|
@@ -160,7 +164,9 @@ class _FlashParser(_ThemedParser):
|
|
|
160
164
|
return super().format_help()
|
|
161
165
|
usage = f"{CLI_NAME} [--debug] [-v] <command> [args]"
|
|
162
166
|
footers = [
|
|
163
|
-
f"new here? run `{CLI_NAME} login`, then `{CLI_NAME}
|
|
167
|
+
f"new here? run `{CLI_NAME} login`, then `{CLI_NAME} env setup`",
|
|
168
|
+
f"train after publishing: `{CLI_NAME} env push --name my-env .`, "
|
|
169
|
+
f"then `{CLI_NAME} train configs/rl.toml`",
|
|
164
170
|
f"any command in depth: `{CLI_NAME} <command> --help`",
|
|
165
171
|
"docs: https://freesolo.co/docs",
|
|
166
172
|
]
|
|
@@ -85,6 +85,8 @@ def _identity_from_verify_body(raw: bytes) -> dict[str, Any]:
|
|
|
85
85
|
or _str_field(user.get("id"))
|
|
86
86
|
),
|
|
87
87
|
"org_id": _str_field(body.get("org_id")) or _str_field(org.get("id")),
|
|
88
|
+
"org_slug": _str_field(body.get("org_slug")) or _str_field(org.get("slug")),
|
|
89
|
+
"org_name": _str_field(body.get("org_name")) or _str_field(org.get("name")),
|
|
88
90
|
"api_key_id": (
|
|
89
91
|
_str_field(body.get("api_key_id"))
|
|
90
92
|
or _str_field(body.get("key_id"))
|
|
@@ -124,7 +126,15 @@ def _external_row(row: dict, token: str, identity: dict[str, Any]) -> dict:
|
|
|
124
126
|
out["key_prefix"] = _external_key_prefix(token, identity)
|
|
125
127
|
if identity.get("email"):
|
|
126
128
|
out["email"] = identity["email"]
|
|
127
|
-
for key in (
|
|
129
|
+
for key in (
|
|
130
|
+
"user_id",
|
|
131
|
+
"org_id",
|
|
132
|
+
"org_slug",
|
|
133
|
+
"org_name",
|
|
134
|
+
"api_key_id",
|
|
135
|
+
"training_agent_job_id",
|
|
136
|
+
"project_id",
|
|
137
|
+
):
|
|
128
138
|
if identity.get(key):
|
|
129
139
|
out[key] = identity[key]
|
|
130
140
|
return out
|
|
@@ -184,13 +194,13 @@ def authenticate(authorization: str | None) -> dict | None:
|
|
|
184
194
|
return out
|
|
185
195
|
if _freesolo_verify(token):
|
|
186
196
|
identity = _cached_identity(token)
|
|
187
|
-
|
|
188
|
-
if not email:
|
|
197
|
+
if not identity.get("org_slug"):
|
|
189
198
|
return None
|
|
199
|
+
email = _identity_email(identity)
|
|
190
200
|
row = db.lookup_key(token) or db.ensure_external_key(
|
|
191
201
|
token,
|
|
192
202
|
key_prefix=_external_key_prefix(token, identity),
|
|
193
|
-
email=email,
|
|
203
|
+
email=email or None,
|
|
194
204
|
)
|
|
195
205
|
return _external_row(row, token, identity) if row is not None else None
|
|
196
206
|
return None
|
|
@@ -23,18 +23,17 @@ _DEFAULT_GITHUB_REPO = "freesolo-co/environment-hub"
|
|
|
23
23
|
_GITHUB_BRANCH = "main"
|
|
24
24
|
_DEFAULT_ENVIRONMENT_FILE = "environment.py"
|
|
25
25
|
# Genuine repo-control directories that live at the ROOT of the environment-hub checkout. A DELETE
|
|
26
|
-
# runs `git rm -r -- <
|
|
26
|
+
# runs `git rm -r -- <org-slug>/<name>` directly against that checkout, so a slug whose namespace is
|
|
27
27
|
# one of these would target tracked repo infrastructure (e.g. `DELETE /v1/envs/.github/workflows`).
|
|
28
|
-
#
|
|
29
|
-
#
|
|
28
|
+
# Org slugs can never be dot-prefixed, so these are never publishable namespaces and are always safe
|
|
29
|
+
# to reject on delete.
|
|
30
30
|
_REPO_CONTROL_TOP_LEVEL_PATHS = {
|
|
31
31
|
".github",
|
|
32
32
|
".git",
|
|
33
33
|
}
|
|
34
34
|
# Top-level segments barred from env-package CONTENTS by `_safe_extract`. A strict SUPERSET of the
|
|
35
35
|
# repo-control set: it also bars a top-level `source/` dir inside a package. `source` is intentionally
|
|
36
|
-
# NOT a repo-control namespace
|
|
37
|
-
# and publish writes `source/<name>` without objection — so the delete validator must reject only
|
|
36
|
+
# NOT a repo-control namespace, so the delete validator must reject only
|
|
38
37
|
# `_REPO_CONTROL_TOP_LEVEL_PATHS`, not this set, or `source/<name>` envs become undeletable.
|
|
39
38
|
_BLOCKED_TOP_LEVEL_PATHS = _REPO_CONTROL_TOP_LEVEL_PATHS | {"source"}
|
|
40
39
|
_TAR_METADATA_TYPES = {
|
|
@@ -57,21 +56,18 @@ class EnvPublishError(Exception):
|
|
|
57
56
|
self.status = status
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
# Internal service key has no per-user email; fixed namespace keeps it stable.
|
|
61
|
-
_INTERNAL_NAMESPACE = "internal-freesolo-co"
|
|
62
|
-
|
|
63
|
-
|
|
64
59
|
def namespace_for(key: dict) -> str:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if
|
|
60
|
+
org = key.get("org") if isinstance(key.get("org"), dict) else {}
|
|
61
|
+
raw = key.get("org_slug") or org.get("slug")
|
|
62
|
+
slug = str(raw or "").strip()
|
|
63
|
+
if not slug:
|
|
69
64
|
raise EnvPublishError(
|
|
70
|
-
"authenticated Freesolo key must include an
|
|
65
|
+
"authenticated Freesolo key must include an org slug (used to derive the hub namespace) — "
|
|
71
66
|
"publish with a key created at https://freesolo.co/sign-in (`flash login`)"
|
|
72
67
|
)
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
if not re.fullmatch(r"[a-z0-9][a-z0-9._-]*", slug):
|
|
69
|
+
raise EnvPublishError("authenticated Freesolo key has an invalid org slug")
|
|
70
|
+
return slug
|
|
75
71
|
|
|
76
72
|
|
|
77
73
|
def _sanitize_name(name: str) -> str:
|
|
@@ -458,9 +454,8 @@ def _validate_slug(slug: str) -> tuple[str, str]:
|
|
|
458
454
|
# An internal-key delete bypasses the namespace-ownership check in delete_package, so this
|
|
459
455
|
# validator is the only barrier, and a request like `DELETE /v1/envs/.github/workflows` would
|
|
460
456
|
# otherwise remove tracked repo infrastructure. Reject ONLY `_REPO_CONTROL_TOP_LEVEL_PATHS`, NOT
|
|
461
|
-
# the wider publish-content blocklist: `source` is a legitimately publishable
|
|
462
|
-
#
|
|
463
|
-
# objection), so blocking it here would leave those envs publishable-but-undeletable.
|
|
457
|
+
# the wider publish-content blocklist: `source` is a legitimately publishable org slug, so
|
|
458
|
+
# blocking it here would leave those envs publishable-but-undeletable.
|
|
464
459
|
if namespace in _REPO_CONTROL_TOP_LEVEL_PATHS:
|
|
465
460
|
raise EnvPublishError(f"invalid env id segment: {namespace!r}")
|
|
466
461
|
return namespace, name
|
|
@@ -587,13 +582,13 @@ def delete_package(*, slug: str, key: dict) -> bool:
|
|
|
587
582
|
|
|
588
583
|
Returns ``True`` when a package was removed and ``False`` when it was already absent
|
|
589
584
|
(idempotent). Authorization mirrors publish's namespace isolation: a user key may delete
|
|
590
|
-
only environments in its own
|
|
591
|
-
|
|
585
|
+
only environments in its own org-slug namespace, while the internal service key
|
|
586
|
+
(``auth_kind == "internal"``) may delete any environment.
|
|
592
587
|
"""
|
|
593
588
|
namespace, name = _validate_slug(slug)
|
|
594
589
|
canonical = f"{namespace}/{name}"
|
|
595
|
-
caller_namespace = namespace_for(key)
|
|
596
|
-
if
|
|
590
|
+
caller_namespace = None if key.get("auth_kind") == "internal" else namespace_for(key)
|
|
591
|
+
if caller_namespace is not None and namespace != caller_namespace:
|
|
597
592
|
raise EnvPublishError(
|
|
598
593
|
"you can only delete environments in your own namespace "
|
|
599
594
|
f"({caller_namespace}/…); got {canonical!r}",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "freesolo-flash-dev"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.30"
|
|
4
4
|
description = "Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
# RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
|
|
@@ -109,7 +109,7 @@ artifacts = ["flash/py.typed"]
|
|
|
109
109
|
# auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
|
|
110
110
|
# PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
|
|
111
111
|
# dev-channel release = bump this (and [project].version) and merge to `dev`.
|
|
112
|
-
version = "0.2.
|
|
112
|
+
version = "0.2.30"
|
|
113
113
|
|
|
114
114
|
[tool.pytest.ini_options]
|
|
115
115
|
pythonpath = ["."]
|
|
@@ -591,7 +591,12 @@ def _identity_for_token(token: str) -> dict[str, str]:
|
|
|
591
591
|
if not token.startswith(_USER_PREFIX):
|
|
592
592
|
return {}
|
|
593
593
|
suffix = token.removeprefix(_USER_PREFIX)
|
|
594
|
-
return {
|
|
594
|
+
return {
|
|
595
|
+
"email": f"u-{suffix}@x",
|
|
596
|
+
"key_prefix": "fslo_test",
|
|
597
|
+
"org_id": f"org-{suffix}",
|
|
598
|
+
"org_slug": f"org-{suffix}",
|
|
599
|
+
}
|
|
595
600
|
|
|
596
601
|
|
|
597
602
|
def test_startup_runs_completion_charge_sweep(monkeypatch, tmp_path):
|
|
@@ -21,11 +21,34 @@ def _registered_subcommands() -> set[str]:
|
|
|
21
21
|
return set(sub.choices)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _registered_env_subcommands() -> set[str]:
|
|
25
|
+
"""The nested environment commands argparse registers under `flash env`."""
|
|
26
|
+
parser = cli._build_parser()
|
|
27
|
+
sub = next(a for a in parser._actions if isinstance(a, argparse._SubParsersAction))
|
|
28
|
+
env_parser = sub.choices["env"]
|
|
29
|
+
env_sub = next(a for a in env_parser._actions if isinstance(a, argparse._SubParsersAction))
|
|
30
|
+
return {f"env {name}" for name in env_sub.choices}
|
|
31
|
+
|
|
32
|
+
|
|
24
33
|
def _catalog_commands() -> set[str]:
|
|
25
|
-
"""The commands
|
|
34
|
+
"""The root commands represented by the themed help grid (_HELP_GROUPS).
|
|
35
|
+
|
|
36
|
+
Rows may show nested commands such as `env push`, but argparse only registers `env`
|
|
37
|
+
at the root level.
|
|
38
|
+
"""
|
|
39
|
+
return {cmd.split()[0] for _, rows in cli._HELP_GROUPS for cmd, _ in rows}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _catalog_rows() -> set[str]:
|
|
43
|
+
"""The display rows listed in the themed help grid (_HELP_GROUPS)."""
|
|
26
44
|
return {cmd for _, rows in cli._HELP_GROUPS for cmd, _ in rows}
|
|
27
45
|
|
|
28
46
|
|
|
47
|
+
def _catalog_env_rows() -> set[str]:
|
|
48
|
+
"""The nested environment rows listed in the themed help grid."""
|
|
49
|
+
return {cmd for cmd in _catalog_rows() if cmd.startswith("env ")}
|
|
50
|
+
|
|
51
|
+
|
|
29
52
|
def test_help_catalog_matches_registered_subcommands() -> None:
|
|
30
53
|
"""Every real subcommand appears once in the themed help, and the help lists no command that
|
|
31
54
|
isn't real — so the grouped `flash --help` can't drift from the actual CLI surface."""
|
|
@@ -41,6 +64,10 @@ def test_help_catalog_matches_registered_subcommands() -> None:
|
|
|
41
64
|
assert len(flat) == len(set(flat)), "a command is listed under more than one help group"
|
|
42
65
|
|
|
43
66
|
|
|
67
|
+
def test_help_catalog_matches_registered_env_subcommands() -> None:
|
|
68
|
+
assert _catalog_env_rows() == _registered_env_subcommands()
|
|
69
|
+
|
|
70
|
+
|
|
44
71
|
def test_help_styled_is_themed_and_exits_zero(monkeypatch, capsys) -> None:
|
|
45
72
|
monkeypatch.setenv("FLASH_STYLE", "1")
|
|
46
73
|
monkeypatch.setenv("NO_COLOR", "1") # layout kept, color dropped — assert on contiguous text
|
|
@@ -53,8 +80,10 @@ def test_help_styled_is_themed_and_exits_zero(monkeypatch, capsys) -> None:
|
|
|
53
80
|
assert "managed LoRA post-training" in out # themed banner (lowercase) vs argparse description
|
|
54
81
|
for title in ("getting started", "catalog", "environments", "training", "serving & export"):
|
|
55
82
|
assert title in out # grouped, not a flat dump
|
|
56
|
-
for cmd in
|
|
83
|
+
for cmd in _catalog_rows():
|
|
57
84
|
assert cmd in out
|
|
85
|
+
assert f"{cli.CLI_NAME} env setup" in out
|
|
86
|
+
assert f"{cli.CLI_NAME} env push --name my-env ." in out
|
|
58
87
|
assert "usage:" in out
|
|
59
88
|
assert f"{cli.CLI_NAME} <command> --help" in out # next-step hint
|
|
60
89
|
|
|
@@ -186,11 +186,11 @@ def test_publish_env_plain_without_progress(stub):
|
|
|
186
186
|
def test_delete_env_sends_delete_to_slug_path(stub):
|
|
187
187
|
url, seen = stub
|
|
188
188
|
client = ApiClient(url, "fslo-user-test")
|
|
189
|
-
out = client.delete_env("
|
|
190
|
-
assert out == {"id": "
|
|
189
|
+
out = client.delete_env("acme/my-env")
|
|
190
|
+
assert out == {"id": "acme/my-env", "deleted": True}
|
|
191
191
|
assert seen["method"] == "DELETE"
|
|
192
192
|
# the namespace/name slug (with its slash) goes straight into the path
|
|
193
|
-
assert seen["path"] == "/v1/envs/
|
|
193
|
+
assert seen["path"] == "/v1/envs/acme/my-env"
|
|
194
194
|
assert seen["auth"] == "Bearer fslo-user-test"
|
|
195
195
|
|
|
196
196
|
|
{freesolo_flash_dev-0.2.29 → freesolo_flash_dev-0.2.30}/tests/test_client_server_integration.py
RENAMED
|
@@ -63,6 +63,7 @@ def _identity_for_token(token: str) -> dict[str, str]:
|
|
|
63
63
|
"email": f"user-{suffix}@example.com",
|
|
64
64
|
"key_prefix": "fslo_test",
|
|
65
65
|
"org_id": f"org-{suffix}",
|
|
66
|
+
"org_slug": f"org-{suffix}",
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
|
|
@@ -176,6 +177,7 @@ def test_health_and_identity_roundtrip(make_client) -> None:
|
|
|
176
177
|
# the JSON body back into a dict the CLI prints.
|
|
177
178
|
assert me["key_prefix"]
|
|
178
179
|
assert "email" in me
|
|
180
|
+
assert "org_slug" in me
|
|
179
181
|
|
|
180
182
|
|
|
181
183
|
def test_create_status_list_cancel_lifecycle(make_client) -> None:
|
|
@@ -73,34 +73,31 @@ def _pkg_b64(files: dict[str, str]) -> str:
|
|
|
73
73
|
return base64.b64encode(buf.getvalue()).decode()
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def
|
|
77
|
-
assert envs.namespace_for({"
|
|
78
|
-
assert envs.namespace_for({"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
def test_namespace_uses_org_slug():
|
|
77
|
+
assert envs.namespace_for({"org_slug": "acme"}) == "acme"
|
|
78
|
+
assert envs.namespace_for({"org": {"slug": "freesolo-co"}}) == "freesolo-co"
|
|
79
|
+
assert envs.namespace_for({"email": "dev@clado.ai", "org_slug": "acme"}) == "acme"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_namespace_requires_org_slug():
|
|
83
|
+
for key in (
|
|
84
|
+
{"id": 7},
|
|
85
|
+
{"key_prefix": "fslo-abc"},
|
|
86
|
+
{},
|
|
87
|
+
{"email": "dev@clado.ai"},
|
|
88
|
+
{"org_name": "Acme"},
|
|
89
|
+
{"org_slug": "Bad Slug"},
|
|
90
|
+
):
|
|
91
|
+
with pytest.raises(envs.EnvPublishError, match="org slug"):
|
|
84
92
|
envs.namespace_for(key)
|
|
85
93
|
|
|
86
94
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
assert (
|
|
94
|
-
envs.namespace_for({"auth_kind": "internal", "email": "missing-email"})
|
|
95
|
-
== envs._INTERNAL_NAMESPACE
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_internal_special_case_does_not_loosen_user_keys():
|
|
100
|
-
# The bypass is reserved for auth_kind == "internal" ONLY: a user key (any other auth_kind, or
|
|
101
|
-
# none) without a valid email must still be rejected so two users can't collide on a namespace.
|
|
102
|
-
for key in ({"auth_kind": "external"}, {"auth_kind": "user", "email": "no-at"}, {"email": ""}):
|
|
103
|
-
with pytest.raises(envs.EnvPublishError, match="email"):
|
|
95
|
+
def test_internal_key_does_not_get_publish_namespace_fallback():
|
|
96
|
+
for key in (
|
|
97
|
+
{"auth_kind": "internal"},
|
|
98
|
+
{"auth_kind": "internal", "email": "internal@freesolo.co"},
|
|
99
|
+
):
|
|
100
|
+
with pytest.raises(envs.EnvPublishError, match="org slug"):
|
|
104
101
|
envs.namespace_for(key)
|
|
105
102
|
|
|
106
103
|
|
|
@@ -130,15 +127,15 @@ def test_publish_uploads_to_github_and_returns_slug(monkeypatch):
|
|
|
130
127
|
ref = envs.publish_package(
|
|
131
128
|
package_b64=_pkg_b64(_MINIMAL),
|
|
132
129
|
name="My Env!",
|
|
133
|
-
key={"email": "dev@clado.ai"},
|
|
130
|
+
key={"email": "dev@clado.ai", "org_slug": "acme"},
|
|
134
131
|
)
|
|
135
132
|
|
|
136
|
-
root = "
|
|
133
|
+
root = "acme/my-env"
|
|
137
134
|
assert ref == root
|
|
138
135
|
assert captured["repo"] == "freesolo-co/environment-hub"
|
|
139
136
|
assert captured["token"] == "ghp-test"
|
|
140
137
|
assert captured["publish_root"] == root
|
|
141
|
-
assert captured["message"] == "Upload Flash environment
|
|
138
|
+
assert captured["message"] == "Upload Flash environment acme/my-env"
|
|
142
139
|
assert captured["files"] == ["environment.py", "pyproject.toml"]
|
|
143
140
|
|
|
144
141
|
|
|
@@ -155,7 +152,7 @@ def test_publish_rejects_bad_input(monkeypatch):
|
|
|
155
152
|
envs.publish_package(
|
|
156
153
|
package_b64=_pkg_b64({"pyproject.toml": "[project]\nname='e'\n"}),
|
|
157
154
|
name="e",
|
|
158
|
-
key={"
|
|
155
|
+
key={"org_slug": "acme"},
|
|
159
156
|
)
|
|
160
157
|
|
|
161
158
|
|
|
@@ -201,7 +198,7 @@ def test_record_published_environment_posts_to_backend(monkeypatch):
|
|
|
201
198
|
monkeypatch.setattr(environment_registry.urllib.request, "urlopen", fake_urlopen)
|
|
202
199
|
|
|
203
200
|
ok = environment_registry.record_published_environment(
|
|
204
|
-
slug="
|
|
201
|
+
slug="acme/my-env",
|
|
205
202
|
name="My Env",
|
|
206
203
|
key={"org_id": "org-1", "user_id": "user-1", "api_key_id": "key-1"},
|
|
207
204
|
)
|
|
@@ -212,11 +209,11 @@ def test_record_published_environment_posts_to_backend(monkeypatch):
|
|
|
212
209
|
body = json.loads(seen["body"])
|
|
213
210
|
assert body == {
|
|
214
211
|
"orgId": "org-1",
|
|
215
|
-
"slug": "
|
|
212
|
+
"slug": "acme/my-env",
|
|
216
213
|
"name": "My Env",
|
|
217
214
|
"hubRepo": "freesolo-co/environment-hub",
|
|
218
215
|
"hubRef": "main",
|
|
219
|
-
"hubPath": "
|
|
216
|
+
"hubPath": "acme/my-env/environment.py",
|
|
220
217
|
"publishedByUserId": "user-1",
|
|
221
218
|
"apiKeyId": "key-1",
|
|
222
219
|
"metadata": {"source": "flash.env.push"},
|
|
@@ -229,7 +226,7 @@ def test_record_published_environment_is_best_effort(monkeypatch):
|
|
|
229
226
|
monkeypatch.delenv("FREESOLO_INTERNAL_KEY", raising=False)
|
|
230
227
|
assert (
|
|
231
228
|
environment_registry.record_published_environment(
|
|
232
|
-
slug="
|
|
229
|
+
slug="acme/my-env",
|
|
233
230
|
name="My Env",
|
|
234
231
|
key={"org_id": "org-1"},
|
|
235
232
|
)
|
|
@@ -262,7 +259,7 @@ def test_record_environment_use_posts_to_backend(monkeypatch):
|
|
|
262
259
|
monkeypatch.setattr(environment_registry.urllib.request, "urlopen", fake_urlopen)
|
|
263
260
|
|
|
264
261
|
ok = environment_registry.record_environment_use(
|
|
265
|
-
slug="
|
|
262
|
+
slug="acme/my-env",
|
|
266
263
|
run_id="flash-1",
|
|
267
264
|
key={"org_id": "org-1"},
|
|
268
265
|
)
|
|
@@ -272,7 +269,7 @@ def test_record_environment_use_posts_to_backend(monkeypatch):
|
|
|
272
269
|
assert seen["headers"]["Authorization"] == "Bearer internal-test"
|
|
273
270
|
assert json.loads(seen["body"]) == {
|
|
274
271
|
"orgId": "org-1",
|
|
275
|
-
"slug": "
|
|
272
|
+
"slug": "acme/my-env",
|
|
276
273
|
"runId": "flash-1",
|
|
277
274
|
}
|
|
278
275
|
|
|
@@ -310,7 +307,7 @@ def test_record_training_run_posts_to_backend(monkeypatch):
|
|
|
310
307
|
"model": "Qwen/Qwen3.5-4B",
|
|
311
308
|
"algorithm": "grpo",
|
|
312
309
|
"phase": "rl",
|
|
313
|
-
"environment": {"id": "
|
|
310
|
+
"environment": {"id": "acme/my-env"},
|
|
314
311
|
"gpu": {"type": "RTX 5090"},
|
|
315
312
|
},
|
|
316
313
|
platform_context={
|
|
@@ -328,7 +325,7 @@ def test_record_training_run_posts_to_backend(monkeypatch):
|
|
|
328
325
|
assert body["orgId"] == "org-1"
|
|
329
326
|
assert body["runId"] == "flash-1"
|
|
330
327
|
assert body["status"] == "running"
|
|
331
|
-
assert body["environmentSlug"] == "
|
|
328
|
+
assert body["environmentSlug"] == "acme/my-env"
|
|
332
329
|
assert body["model"] == "Qwen/Qwen3.5-4B"
|
|
333
330
|
|
|
334
331
|
|
|
@@ -571,10 +568,10 @@ def test_github_publish_retries_concurrent_push(monkeypatch, tmp_path):
|
|
|
571
568
|
monkeypatch.setattr(envs, "_github_publish_once", fake_publish_once)
|
|
572
569
|
monkeypatch.setattr(envs.time, "sleep", lambda _seconds: None)
|
|
573
570
|
|
|
574
|
-
ref = envs._github_publish(tmp_path, name="e", key={"
|
|
571
|
+
ref = envs._github_publish(tmp_path, name="e", key={"org_slug": "acme"})
|
|
575
572
|
|
|
576
573
|
assert calls["count"] == 2
|
|
577
|
-
assert ref == "
|
|
574
|
+
assert ref == "acme/e"
|
|
578
575
|
|
|
579
576
|
|
|
580
577
|
def _git(cwd: Path, *args: str) -> None:
|
|
@@ -631,9 +628,9 @@ def test_delete_package_user_can_delete_own_namespace(monkeypatch):
|
|
|
631
628
|
|
|
632
629
|
monkeypatch.setattr(envs, "_github_delete", fake_github_delete)
|
|
633
630
|
assert (
|
|
634
|
-
envs.delete_package(slug="
|
|
631
|
+
envs.delete_package(slug="acme/my-env", key={"org_slug": "acme"}) is True
|
|
635
632
|
)
|
|
636
|
-
assert seen == {"slug": "
|
|
633
|
+
assert seen == {"slug": "acme/my-env", "token": "ghp-test"}
|
|
637
634
|
|
|
638
635
|
|
|
639
636
|
def test_delete_package_rejects_other_users_namespace(monkeypatch):
|
|
@@ -642,7 +639,7 @@ def test_delete_package_rejects_other_users_namespace(monkeypatch):
|
|
|
642
639
|
envs, "_github_delete", lambda *a, **k: pytest.fail("storage must not be touched")
|
|
643
640
|
)
|
|
644
641
|
with pytest.raises(envs.EnvPublishError) as excinfo:
|
|
645
|
-
envs.delete_package(slug="someone-else/env", key={"
|
|
642
|
+
envs.delete_package(slug="someone-else/env", key={"org_slug": "acme"})
|
|
646
643
|
assert excinfo.value.status == 403
|
|
647
644
|
|
|
648
645
|
|
|
@@ -652,18 +649,16 @@ def test_delete_package_internal_key_can_delete_any_namespace(monkeypatch):
|
|
|
652
649
|
monkeypatch.setattr(
|
|
653
650
|
envs, "_github_delete", lambda slug, *, token: seen.update(slug=slug) or True
|
|
654
651
|
)
|
|
655
|
-
assert (
|
|
656
|
-
|
|
657
|
-
)
|
|
658
|
-
assert seen["slug"] == "dev-clado-ai/paper-foo"
|
|
652
|
+
assert envs.delete_package(slug="acme/paper-foo", key={"auth_kind": "internal"}) is True
|
|
653
|
+
assert seen["slug"] == "acme/paper-foo"
|
|
659
654
|
|
|
660
655
|
|
|
661
656
|
def test_delete_package_internal_key_rejects_repo_control_namespace(monkeypatch):
|
|
662
657
|
# The internal key bypasses the namespace-ownership check, so _validate_slug is the ONLY barrier
|
|
663
658
|
# before `git rm -r -- <namespace>/<name>`. A GENUINE repo-control top-level path (a dir at the
|
|
664
659
|
# root of the hub checkout) must be rejected so e.g. DELETE /v1/envs/.github/workflows can't
|
|
665
|
-
# remove tracked repo infrastructure. Only `.git`/`.github` qualify
|
|
666
|
-
#
|
|
660
|
+
# remove tracked repo infrastructure. Only `.git`/`.github` qualify because org slugs can never
|
|
661
|
+
# be dot-prefixed, so these are never publishable.
|
|
667
662
|
monkeypatch.setenv("GITHUB_TOKEN", "ghp-test")
|
|
668
663
|
monkeypatch.setattr(
|
|
669
664
|
envs, "_github_delete", lambda *a, **k: pytest.fail("storage must not be touched")
|
|
@@ -677,10 +672,9 @@ def test_delete_package_internal_key_rejects_repo_control_namespace(monkeypatch)
|
|
|
677
672
|
|
|
678
673
|
def test_delete_package_allows_publishable_source_namespace(monkeypatch):
|
|
679
674
|
# Regression guard for publish/delete symmetry: `source` is in publish's package-CONTENT
|
|
680
|
-
# blocklist (_BLOCKED_TOP_LEVEL_PATHS via _safe_extract) but is a legitimate
|
|
681
|
-
#
|
|
682
|
-
#
|
|
683
|
-
# envs would be publishable-but-undeletable.
|
|
675
|
+
# blocklist (_BLOCKED_TOP_LEVEL_PATHS via _safe_extract) but is a legitimate org namespace.
|
|
676
|
+
# Delete must therefore reach storage for `source/<name>` (not 400), or those envs would be
|
|
677
|
+
# publishable-but-undeletable.
|
|
684
678
|
monkeypatch.setenv("GITHUB_TOKEN", "ghp-test")
|
|
685
679
|
seen: dict[str, str] = {}
|
|
686
680
|
|
|
@@ -689,8 +683,8 @@ def test_delete_package_allows_publishable_source_namespace(monkeypatch):
|
|
|
689
683
|
return True
|
|
690
684
|
|
|
691
685
|
monkeypatch.setattr(envs, "_github_delete", fake_delete)
|
|
692
|
-
# A user key whose
|
|
693
|
-
source_key = {"
|
|
686
|
+
# A user key whose org slug is `source` deletes its OWN `source/<name>`.
|
|
687
|
+
source_key = {"org_slug": "source"}
|
|
694
688
|
assert envs.namespace_for(source_key) == "source"
|
|
695
689
|
assert "source" in envs._BLOCKED_TOP_LEVEL_PATHS # still barred from package CONTENTS
|
|
696
690
|
assert envs.delete_package(slug="source/my-env", key=source_key) is True
|
|
@@ -718,7 +712,7 @@ def test_delete_package_validates_slug(monkeypatch):
|
|
|
718
712
|
|
|
719
713
|
|
|
720
714
|
def test_canonical_env_id_accepts_only_canonical_form():
|
|
721
|
-
assert envs.canonical_env_id("
|
|
715
|
+
assert envs.canonical_env_id("acme/my-env") == "acme/my-env"
|
|
722
716
|
for bad in ("ns/env/", " ns/env", "ns/env ", "ns/env%20", "Ns/Env", "noslash"):
|
|
723
717
|
with pytest.raises(envs.EnvPublishError):
|
|
724
718
|
envs.canonical_env_id(bad)
|
|
@@ -727,7 +721,7 @@ def test_canonical_env_id_accepts_only_canonical_form():
|
|
|
727
721
|
def test_delete_package_requires_github_token(monkeypatch):
|
|
728
722
|
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
|
|
729
723
|
with pytest.raises(envs.EnvPublishError) as excinfo:
|
|
730
|
-
envs.delete_package(slug="
|
|
724
|
+
envs.delete_package(slug="acme/x", key={"auth_kind": "internal"})
|
|
731
725
|
assert excinfo.value.status == 503
|
|
732
726
|
assert "GITHUB_TOKEN" in str(excinfo.value)
|
|
733
727
|
|
|
@@ -908,7 +902,7 @@ def test_record_deleted_environment_sends_delete(monkeypatch):
|
|
|
908
902
|
monkeypatch.setattr(environment_registry.urllib.request, "urlopen", fake_urlopen)
|
|
909
903
|
|
|
910
904
|
ok = environment_registry.record_deleted_environment(
|
|
911
|
-
slug="
|
|
905
|
+
slug="acme/my-env",
|
|
912
906
|
key={"org_id": "org-1"},
|
|
913
907
|
)
|
|
914
908
|
|
|
@@ -916,7 +910,7 @@ def test_record_deleted_environment_sends_delete(monkeypatch):
|
|
|
916
910
|
assert seen["url"] == "https://backend.test/api/flash/environments/internal"
|
|
917
911
|
assert seen["method"] == "DELETE"
|
|
918
912
|
assert seen["headers"]["Authorization"] == "Bearer internal-test"
|
|
919
|
-
assert json.loads(seen["body"]) == {"orgId": "org-1", "slug": "
|
|
913
|
+
assert json.loads(seen["body"]) == {"orgId": "org-1", "slug": "acme/my-env"}
|
|
920
914
|
|
|
921
915
|
|
|
922
916
|
def test_record_deleted_environment_is_best_effort(monkeypatch):
|
|
@@ -925,7 +919,7 @@ def test_record_deleted_environment_is_best_effort(monkeypatch):
|
|
|
925
919
|
monkeypatch.delenv("FREESOLO_INTERNAL_KEY", raising=False)
|
|
926
920
|
assert (
|
|
927
921
|
environment_registry.record_deleted_environment(
|
|
928
|
-
slug="
|
|
922
|
+
slug="acme/my-env",
|
|
929
923
|
key={"org_id": "org-1"},
|
|
930
924
|
)
|
|
931
925
|
is False
|