vaultlayer 0.1.76__tar.gz → 0.1.78__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.
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/PKG-INFO +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/pyproject.toml +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/startup_scripts.py +70 -4
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/tester/agent.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/email.py +13 -6
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/job_routes.py +105 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_preflight.py +180 -8
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_remote.py +17 -11
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/connect.py +4 -4
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/data.py +3 -3
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/estimate.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/examples.py +8 -8
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/gpus.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/inject.py +25 -5
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/main.py +18 -17
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/notify.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/ps.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/run.py +8 -2
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/signup.py +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/sync.py +6 -6
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/vaultlayer_data.json +3 -3
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer/_resume_state.py +158 -30
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer/_resume_torch.py +52 -14
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/PKG-INFO +1 -1
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/SOURCES.txt +1 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_api_coverage.py +19 -2
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_audit_gaps.py +14 -3
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_docs_command_parity.py +1 -1
- vaultlayer-0.1.78/tests/test_emergency_save_guards.py +244 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_inject_coverage.py +38 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_preflight_safety.py +273 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_remote_command_contract.py +51 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_hook.py +75 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_startup_scripts.py +81 -2
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/README.md +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/setup.cfg +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/gang.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/migration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/provider_startup.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/aws.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/aws_capacity.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/azure.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/base.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/coreweave.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/crusoe.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/gcp.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/gcp_capacity.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/hyperstack.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/lambda_labs.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/nebius.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/region_planner.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/runpod.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/runpod_cluster.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/vast_ai.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/providers/voltage_park.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/ssh.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/broker/warm_pool.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/finops/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/finops/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/ingestion.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/inject.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/mount.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/namespace/prefetch.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/orchestration/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/orchestration/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/orchestration/decisions.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/orchestration/egress.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/orchestration/prompts.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/pricing/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/pricing/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/pricing/pollers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/tester/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/vault/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/vault/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/vault/delta.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/vault/multipart.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/vault/r2.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/watchdog/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/watchdog/agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/agents/watchdog/signals.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/_errors.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/admin_dashboard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/admin_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/auth.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/bill_reconcile.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/credentials.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/credits.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/db.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/feedback_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/flags.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/fraud.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/gmail_welcome.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/internal_welcome_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/invite_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/job_worker.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/main.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/models.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/org_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/pricing_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/smoke_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/spend_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/sso.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/stripe_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/test_admin_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/unified_queue.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/unified_queue_persist.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/api/user_admin_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_auth_recovery.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_dataset.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_image_preflight.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_pricing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/_subprocess.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/admin_cmd.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/audit_export.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/byoc_config_template.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/checkpoint_template.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/checkpoint_template_deepspeed.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/checkpoint_template_jax.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/crash_reporter.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/credits_cmd.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/delete.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/download.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/env_check_gen.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/feedback.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/login_cmd.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/migrate_keys.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/remote_providers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/restart.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/cli/smoke_history.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/api_utils.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/aws_box.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/bad_hosts.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/base_image_manifest.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/bill_auditor.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/byoc_aws_preflight.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/byoc_registry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/byoc_storage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/compute_tiers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/config.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/cred_at_rest.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/credential_vault.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/credits.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/dashboard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/data_loader.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/email.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/env_spec.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/env_utils.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/estimate_calibration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/failure_codes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/gpu_resolver.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/gpu_sizing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/host_reputation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/idempotency.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/invoice.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/job_logger.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/job_run_logger.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/job_state_store.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/live_reconcile.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/log_redaction.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/logging_utils.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/low_churn_filter.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/manifest.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/models.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/models_additions.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/offer_validation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/price_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provider_allowlist.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provider_billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provider_capacity.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provider_health.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provider_http_retry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/provision_concurrency.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/queue.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/quote.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/r2_credentials.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/r2_utils.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/recovery_tiers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/retry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/run_telemetry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/schema_health.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/script_upload.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/security.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/server_audit.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/sla.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/spend.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/storage_billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/sts.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/suggestions.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/telemetry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/shared/usage_tracker.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer/_resume_hook.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/dependency_links.txt +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/entry_points.txt +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/requires.txt +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/vaultlayer.egg-info/top_level.txt +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/workers/__init__.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/workers/claude_bug_fixer.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/src/workers/feedback_worker.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_cron_cleanup_constant_time.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_dashboard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_invite_cli.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_mint_user_token.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_runpod_auth.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_secret_constant_time.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_self_auth_cli.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_admin_user_stats.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_agents_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_agents_misc_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_all_migrations_apply.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_attribution_patch.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_audit_billing_security.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_audit_export.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_audit_p0_leak_fixes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_credits.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_onboarding.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_recovery.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_rollout_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_user_id_unification.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_auth_verify_bruteforce.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_backoff_split.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_byoc_gang.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_default_image.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_dlami_resolver.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_error_formatting.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_instance_map_consistency.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_managed_cred_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_network_ebs.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_od_allowlist_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_orphan_gang_aware.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_ranker_fallback.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_residency_lock.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_spot_interruption_detect.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_termination_retry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_aws_userdata_shim.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_azure_disk_and_adopt.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_azure_managed_cred_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_balance_floor.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_balance_low_cli_render.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_balance_low_warning.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_base_image_cosign_verify.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_base_image_digest_pin.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_big_model_sizing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_bill_reconcile.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_billing_estimate_and_rate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_billing_freeze_cleared_on_failure.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_billing_ledger_from_job_runs.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_billing_ledger_grants.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_boot_phase_wiring.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_broker_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_budget_cap.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_allowlist_bypass.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_aws_preflight.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_broker_isolation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_checkpoint_store.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_config_template.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_customer_only_logs.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_customer_orphan_scan.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_data_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_dataset_id_block.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_dormant_path_hygiene.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_env_file_permissions.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_nons3_checkpoint_reader.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_phase2.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_provider_env_scrub.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_provider_registry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_resume_preserve.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_run_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_same_provider_pin.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_scoped_s3_creds.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_server_queue.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_storage_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_stuck_boot_strand.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_teardown_creds.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_terminal_secret_purge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_termination_alerts.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_tier4_recovery.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_byoc_wallet_backstops.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cancel_bridge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_canonical_rls_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkout_landing_pages.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_dcp_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_failclosed_nhop.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_helper.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_readiness_fence.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_template_dcp_parity.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_checkpoint_template_fsdp_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_arg_ergonomics.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_feature_bypass.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_pinned_provider_quote.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_run_summary.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_spend.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cli_version.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cloud_provider_catalog.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cold_start_race.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_compile_crash_classification.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_completion_failed_events.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_config_env_writers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_connect_ux.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_constraint_satisfaction.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_constraint_satisfaction_integration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_coreweave_fence_postdelete.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_coverage_gaps.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cred_at_rest.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_credentials_api.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_credits_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_credits_ownership.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_credits_ratio_constant.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_critical_gaps_batch1.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ctrlc_confirm.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_cuda_mismatch_selfheal.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dashboard_cmd.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dashboard_identity_bridge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dashboard_pin_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_data_loader_ttl.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dataset_namespacing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dataset_purge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dataset_warmup_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_datasets.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_db_admin_authz.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_db_helpers_propagate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_dcp_checkpoint.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_debug_capture.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_delete_job_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_diagnose_command.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_download_byoc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_download_placeholder.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_e2e_aws_down.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_e2e_smoke.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ec4_weight_storm.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_egress_engine.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_elastic_gang_restart.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_env_check.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_env_check_imports_from.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_env_check_timeout.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_env_config_token_mismatch_warning.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_env_spec.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_estimate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_estimate_calibration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_estimate_range.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_estimate_runtime_calibration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_exit_code_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_expand_consent_durability.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_failover_at_persistence.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_failover_credit_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_failure_attribution.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_failure_taxonomy.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_feedback_snapshot_export.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_feedback_system.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_fence_confirmation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_find_checkpoint.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_finops_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_first_run_wizard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_flat_markup_byoc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_flow_abc_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_flow_abc_smoke.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gainshare.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gap27_shard_sha256.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gaps_batch2.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gcp_cred_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gcp_image_and_serial.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gcp_local_ssd.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gcp_zone_fallback.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_get_live_price_real.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gmail_welcome.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_catalog_resolves.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_contract.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_metrics.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_metrics_error_surfacing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_resolver.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpu_sizing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gpus_command.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_gtm_gaps.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_handle_provision_result.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_heartbeat_field.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_hf_cache_retention.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_hf_mirror.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_hf_model_cache.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_home_isolation_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_host_quality_and_blacklist.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_host_reputation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_idem3_vault_orphan_gc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_idempotency.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_included_providers.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_init_login_signup.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_instance_lost_completion.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_integration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_internal_checkout_bridge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_internal_welcome_bridge.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_invite_tokens.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_invite_update.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_is_test_account.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_job_contract.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_job_logs_cursor_pagination.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_keep_alive.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_lambda_terminate_timeout.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_legacy_credit_endpoints_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_list_jobs_columns.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_list_jobs_run_type_filter.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_liveness_cloud_orphan_framework.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_log_dedup_final_post.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_log_flush_retry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_log_redaction.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_logs_keepalive.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_logs_ux.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_managed_balance_retry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_markup_resolution.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_matrix_health.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_cap_nhop.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_double_start_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_leg_reset.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_nxn.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_ssh_failure_fence.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_migration_stale_rate_leak.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_min_cuda_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_money_display_ledger.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_multi_node_guard.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_multinode_aws_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_multinode_distributed_mvp.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_multinode_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_namespace_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_nebius_managed_creds.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_new_account_guards.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_new_features.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_nhop_multihop_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_no_phantom_columns.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_node_auth_owner_resolution.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ops_ux_cleanup.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_orchestration_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_org_accounts.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_org_rbac_sso.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_orphan_reservation_release.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_orphan_scanner.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ownership_db_fallback.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_p2_billing_correctness.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_p2_credit_safety.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_password_reset.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_password_signup.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_phase_stuck_budgets.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_pin_auth.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_placement_metadata.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_precompile_check.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_preemption_attribution.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_preemption_machine_exclusion.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_prefers_different_provider_routing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_prefetch_r2_prefix.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_preflight_data.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_pricing_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_proactive_spot_flush.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_prod_isolation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_error_sanitization.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_health.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_matrix.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_name_leaks.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_smoke.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_smoke_prod.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_startup.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provider_testing_state.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provision_error_message.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_provision_lock_renewal.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ps_duration.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_qa_round_cli_ux.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_queue_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_quota_preflight_initial_provision.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_r2_credentials.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_rbac.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_reconcile_backfill.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_recovery_job_intent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_recovery_resilience.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_referral_reward.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_region_constraint_enforcement.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_registry_managed_path.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_remaining_audit_gaps.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_remote_managed_poll.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_remote_ux_quick_wins.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_requirements_template.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_reservation_floor_1h.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_reservation_settle_balance.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_reset_pin_cli.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_restart_balance_errors.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_restart_cancelled_flow.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_restart_cli.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_restart_recovery.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_alias.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_chain_credscope.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_checkpoint_threading.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_failfast_nhop.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_resume_leg_reservation_and_budget.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_rexeed_ux.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_rotate_token.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_run_command_leaked_flag.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_run_data_flag.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_run_health_rpc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_run_managed_default.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_run_telemetry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_scenarios.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_schema_health.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_script_upload.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_security.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_security_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_server_audit.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_server_lifecycle.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_settle_idempotent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_settle_nhop_atomicity.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_setup_script.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_shared_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_shared_utils.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_signup_account_rpc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_signup_decay.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_sla_breach.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_sla_latency.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_spend_attribution_propagation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_spend_cap_atomic.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_spend_endpoint_rls.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_spend_id_fallback.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_spend_surenoobster_e2e.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_ssh_resume_checkpoint_resolution.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_start_job_reservation_flows.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_status_secret_leak.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_stop_terminate_verify.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_storage_billing_coverage.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_storage_cred_dryrun.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_sts_concurrent_cache.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_stuck_leg_billing.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_stuck_watchdog.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_submit_reservation.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_submit_reservation_gate.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_submit_run_new_account_guards.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_sync_cloud_mirror.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_sync_s3_mirror.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_technical_cracks.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_telemetry.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_telemetry_reconcile.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_termination_guarantee_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_test_admin_routes.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_test_bypass_gates.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_unified_queue.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_unified_queue_hardening.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_unified_queue_persist.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_v0_gaps.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_vast_error_redaction.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_vast_onstart_limit.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_vault_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_verification_code_rpc.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_watchdog_agent.py +0 -0
- {vaultlayer-0.1.76 → vaultlayer-0.1.78}/tests/test_watchdog_runpod_termination.py +0 -0
|
@@ -1082,11 +1082,77 @@ def _checkpoint_helper_lines(job: "Job", dest_dir_expr: str) -> list[str]:
|
|
|
1082
1082
|
|
|
1083
1083
|
_FRAMEWORK_TRAINER_IMPORTS = ("transformers", "trl", "pytorch_lightning", "lightning")
|
|
1084
1084
|
|
|
1085
|
+
# Launchers that start an HF/TRL/Lightning Trainer in a SUBPROCESS. The trainer
|
|
1086
|
+
# import never appears in the script we scan (`axolotl train cfg.yml` reaches
|
|
1087
|
+
# transformers via `accelerate launch` -> `python -m axolotl.cli.train`), so
|
|
1088
|
+
# detected_imports and the inline-script scan both miss it, no hook is shipped,
|
|
1089
|
+
# and resume silently restarts at step 0 with checkpoints sitting in R2. The
|
|
1090
|
+
# import scan alone is only correct when the user's script IS the trainer.
|
|
1091
|
+
_FRAMEWORK_TRAINER_LAUNCHERS = frozenset({
|
|
1092
|
+
"axolotl", "accelerate", "deepspeed", "torchrun", "llamafactory-cli",
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
# `-m` modules whose ROOT package is too broad to match on. `torchrun` is a thin
|
|
1096
|
+
# console-script over `torch.distributed.run`, and both spellings are common, but
|
|
1097
|
+
# matching the root "torch" would ship the hook for every `python -m torch.*`
|
|
1098
|
+
# (e.g. torch.utils.bottleneck). Matched in FULL, never by root.
|
|
1099
|
+
_FRAMEWORK_TRAINER_LAUNCH_MODULES = frozenset({
|
|
1100
|
+
"torch.distributed.run",
|
|
1101
|
+
"torch.distributed.launch", # deprecated alias, still widely used
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def _command_uses_framework_launcher(command: str) -> bool:
|
|
1106
|
+
"""True if ``command`` invokes a known subprocess trainer launcher.
|
|
1107
|
+
|
|
1108
|
+
Matches on the executable BASENAME (so ``/usr/local/bin/accelerate`` counts),
|
|
1109
|
+
on the ROOT of a ``-m`` module (``python -m axolotl.cli.train`` -> axolotl),
|
|
1110
|
+
and on a FULL ``-m`` module path for launchers whose root is too broad to
|
|
1111
|
+
match (``python -m torch.distributed.run``). Flags are skipped, so
|
|
1112
|
+
``python train.py --deepspeed ds.json`` does NOT match on the flag, and a
|
|
1113
|
+
script merely NAMED after a launcher (``python axolotl_test.py``) does not
|
|
1114
|
+
match either -- both would be false positives that ship the ~20KB hook to
|
|
1115
|
+
jobs that cannot use it.
|
|
1116
|
+
"""
|
|
1117
|
+
# Guard non-str BEFORE shlex: shlex.split() treats a non-str as a file-like
|
|
1118
|
+
# stream, reads chars off it, and dies deep inside on `nextchar in whitespace`
|
|
1119
|
+
# (TypeError, not ValueError). job.command is "" on older rows and a Mock in
|
|
1120
|
+
# tests that build scripts from stubbed jobs.
|
|
1121
|
+
if not isinstance(command, str) or not command:
|
|
1122
|
+
return False
|
|
1123
|
+
try:
|
|
1124
|
+
tokens = shlex.split(command)
|
|
1125
|
+
except ValueError: # unbalanced quotes -- degrade, never raise
|
|
1126
|
+
tokens = command.split()
|
|
1127
|
+
expect_module = False
|
|
1128
|
+
for tok in tokens:
|
|
1129
|
+
if expect_module:
|
|
1130
|
+
expect_module = False
|
|
1131
|
+
if tok in _FRAMEWORK_TRAINER_LAUNCH_MODULES:
|
|
1132
|
+
return True
|
|
1133
|
+
if tok.split(".", 1)[0] in _FRAMEWORK_TRAINER_LAUNCHERS:
|
|
1134
|
+
return True
|
|
1135
|
+
continue
|
|
1136
|
+
if tok == "-m":
|
|
1137
|
+
expect_module = True
|
|
1138
|
+
continue
|
|
1139
|
+
if tok.startswith("-"):
|
|
1140
|
+
continue
|
|
1141
|
+
# Node paths are posix; rsplit keeps this correct when the broker runs
|
|
1142
|
+
# on Windows (os.path.basename would not split "/" there).
|
|
1143
|
+
name = tok.replace("\\", "/").rsplit("/", 1)[-1]
|
|
1144
|
+
if name in _FRAMEWORK_TRAINER_LAUNCHERS:
|
|
1145
|
+
return True
|
|
1146
|
+
return False
|
|
1147
|
+
|
|
1085
1148
|
|
|
1086
1149
|
def _job_uses_framework_trainer(job: "Job") -> bool:
|
|
1087
|
-
"""True if the job
|
|
1088
|
-
|
|
1089
|
-
|
|
1150
|
+
"""True if the job runs an HF/TRL/Lightning Trainer (so it needs the resume
|
|
1151
|
+
hook materialized). Checks the launcher command FIRST -- a subprocess launcher
|
|
1152
|
+
imports the trainer out of view of both scans below -- then falls back to
|
|
1153
|
+
runtime_spec.detected_imports, then to an inline-script scan."""
|
|
1154
|
+
if _command_uses_framework_launcher(getattr(job, "command", "") or ""):
|
|
1155
|
+
return True
|
|
1090
1156
|
spec = _extract_runtime_spec(job)
|
|
1091
1157
|
if spec is not None:
|
|
1092
1158
|
detected = set(spec.detected_imports or [])
|
|
@@ -2851,7 +2917,7 @@ def build_vm_startup_script(broker, job: "Job", *, launch_provider: str = "") ->
|
|
|
2851
2917
|
"touch $_VL_LOG",
|
|
2852
2918
|
"echo '[VL] VM bootstrap started' | tee -a $_VL_LOG",
|
|
2853
2919
|
# Ubuntu 24.04 ships only python3; ensure 'python' resolves so users can
|
|
2854
|
-
# run `vaultlayer run
|
|
2920
|
+
# run `vaultlayer run train.py` without getting exit code 127.
|
|
2855
2921
|
"python --version >/dev/null 2>&1 || "
|
|
2856
2922
|
"ln -sf \"$(command -v python3)\" /usr/local/bin/python 2>/dev/null || true",
|
|
2857
2923
|
]
|
|
@@ -10,7 +10,7 @@ PROBLEM_STATEMENT = """
|
|
|
10
10
|
VaultLayer wraps AI training commands and provides:
|
|
11
11
|
1. Cost savings: 60-70% cheaper than AWS On-Demand by routing to Lambda Labs/CoreWeave
|
|
12
12
|
2. Reliability: 99.9% job completion — detect Spot terminations, checkpoint, auto-resume
|
|
13
|
-
3. Zero code changes: user just runs 'vaultlayer run
|
|
13
|
+
3. Zero code changes: user just runs 'vaultlayer run train.py'
|
|
14
14
|
4. Autonomous: an Orchestration Agent (Claude-powered) makes all decisions
|
|
15
15
|
5. Checkpoints: stored in Cloudflare R2, restorable on any provider
|
|
16
16
|
"""
|
|
@@ -81,7 +81,7 @@ def _build_welcome_bodies() -> tuple[str, str, str]:
|
|
|
81
81
|
f" Add them at {dash}/dashboard/billing (or run `vl credits buy`).\n"
|
|
82
82
|
"\n"
|
|
83
83
|
"5. Run your first job\n"
|
|
84
|
-
" vl run
|
|
84
|
+
" vl run train.py\n"
|
|
85
85
|
"\n"
|
|
86
86
|
f"Open {dash}/dashboard to watch your jobs, GPUs, and balance.\n"
|
|
87
87
|
"\n"
|
|
@@ -112,9 +112,9 @@ def _build_welcome_bodies() -> tuple[str, str, str]:
|
|
|
112
112
|
<a href="{dash}/dashboard/billing">billing</a> page (the CLI equivalent is
|
|
113
113
|
<code>vl credits buy</code>).</p>
|
|
114
114
|
|
|
115
|
-
<p><strong>5. Run your first job</strong> — just
|
|
116
|
-
|
|
117
|
-
<pre style="{_code}">vl run
|
|
115
|
+
<p><strong>5. Run your first job</strong> — just point <code>vl run</code>
|
|
116
|
+
at your training script:</p>
|
|
117
|
+
<pre style="{_code}">vl run train.py</pre>
|
|
118
118
|
|
|
119
119
|
<p><a href="{dash}/dashboard">Open your dashboard</a> to watch your jobs,
|
|
120
120
|
GPUs, and balance.</p>
|
|
@@ -285,7 +285,14 @@ async def send_topup_confirmation(
|
|
|
285
285
|
package: str,
|
|
286
286
|
):
|
|
287
287
|
"""Sent after a successful Stripe payment."""
|
|
288
|
-
|
|
288
|
+
# "Amount paid": fixed packages have a set price — and it can't be back-derived
|
|
289
|
+
# from the credit count because package credits INCLUDE a bonus (growth $50→5,500,
|
|
290
|
+
# scale $200→24,000). A custom top-up has no bonus, so its dollars are exactly
|
|
291
|
+
# credits / CREDITS_PER_USD. Deriving from the package NAME alone returned $0 for
|
|
292
|
+
# every 'custom' purchase — i.e. every dashboard buy, the launch path.
|
|
293
|
+
usd_paid = {"starter": 10.0, "growth": 50.0, "scale": 200.0}.get(
|
|
294
|
+
package, credits_added / CREDITS_PER_USD
|
|
295
|
+
)
|
|
289
296
|
await _send(
|
|
290
297
|
to=email,
|
|
291
298
|
subject=f"VaultLayer — {credits_added:,} credits added",
|
|
@@ -298,7 +305,7 @@ async def send_topup_confirmation(
|
|
|
298
305
|
</tr>
|
|
299
306
|
<tr>
|
|
300
307
|
<td style="padding:6px 16px 6px 0;color:#666;">Amount paid</td>
|
|
301
|
-
<td style="padding:6px 0;"><strong>${usd_paid}</strong></td>
|
|
308
|
+
<td style="padding:6px 0;"><strong>${usd_paid:,.2f}</strong></td>
|
|
302
309
|
</tr>
|
|
303
310
|
<tr>
|
|
304
311
|
<td style="padding:6px 16px 6px 0;color:#666;">Credits added</td>
|
|
@@ -460,7 +460,19 @@ def _resolve_prior_submit_state(job_id: str) -> dict:
|
|
|
460
460
|
if result.data:
|
|
461
461
|
qs = result.data[0].get("queue_state")
|
|
462
462
|
if isinstance(qs, dict):
|
|
463
|
-
|
|
463
|
+
# Durable queue_state persists credential material encrypted at rest:
|
|
464
|
+
# `environment` collapses to a single {"__vlenc_env__": "VLENC1:..."}
|
|
465
|
+
# entry, and registry_password to a "VLENC1:" string
|
|
466
|
+
# (shared.cred_at_rest.encrypt_queue_state_secrets, called on the
|
|
467
|
+
# write path). Returning it RAW made the environment inherit adopt
|
|
468
|
+
# that sentinel as if it were the user's env -- reproducing the exact
|
|
469
|
+
# resume failure the inherit exists to prevent, but only for
|
|
470
|
+
# DB-backed ancestors, i.e. after an API restart when _active_jobs no
|
|
471
|
+
# longer holds the prior leg. Idempotent on legacy plaintext rows;
|
|
472
|
+
# fails closed to {} on a decrypt error, which callers already treat
|
|
473
|
+
# as "not set".
|
|
474
|
+
from shared.cred_at_rest import decrypt_queue_state_secrets
|
|
475
|
+
return decrypt_queue_state_secrets(qs)
|
|
464
476
|
except Exception:
|
|
465
477
|
pass
|
|
466
478
|
return {}
|
|
@@ -517,6 +529,85 @@ def _coerce_inherited_value(field: str, value):
|
|
|
517
529
|
return None
|
|
518
530
|
|
|
519
531
|
|
|
532
|
+
def _inherit_environment_from_chain(body, resume_chain: list[str]) -> bool:
|
|
533
|
+
"""Carry the original leg's `--env` values onto a resume submit.
|
|
534
|
+
|
|
535
|
+
Neither carry layer covered `environment`, so every resume leg started with an
|
|
536
|
+
EMPTY env. A script reading `os.environ.get("MODEL_ID", <default>)` silently
|
|
537
|
+
fell back to its default, loaded a DIFFERENT model, then resumed from the prior
|
|
538
|
+
model's checkpoint -- surfacing as
|
|
539
|
+
`ValueError: loaded state dict contains a parameter group that doesn't match`.
|
|
540
|
+
Two models with compatible param groups would have resumed with NO error at all.
|
|
541
|
+
It also dropped HF_TOKEN (gated models 401 on every resume) and any
|
|
542
|
+
hyperparameters passed via --env.
|
|
543
|
+
|
|
544
|
+
Server-side is the ONLY possible carry path: `environment` is deliberately
|
|
545
|
+
excluded from _SAFE_JOB_RESPONSE_FIELDS (it holds HF_TOKEN), so `vl restart`
|
|
546
|
+
never sees it and cannot resend it. Inheriting here keeps the secret server-side.
|
|
547
|
+
|
|
548
|
+
NOT gated on `inherit_constraints`: `--no-carry` exists to drop stale
|
|
549
|
+
ROUTING/BUDGET constraints. Dropping the environment would strip MODEL_ID and
|
|
550
|
+
HF_TOKEN and break the run outright -- an escape hatch that bricks the job.
|
|
551
|
+
|
|
552
|
+
MERGED per key, NOT all-or-nothing. The original version bailed out whenever the
|
|
553
|
+
body carried ANY environment ("the caller specified one, honour it"), mirroring
|
|
554
|
+
the is_unset predicate _inherit_constraints_from_chain uses for scalars. That was
|
|
555
|
+
wrong, and it made this whole carry a no-op in production: cli/_remote.py scrapes
|
|
556
|
+
the user's SHELL on every submit, including `vl restart` --
|
|
557
|
+
|
|
558
|
+
_FORWARD_PREFIXES = ("VL_USER_", "WANDB_", "HF_", "HUGGING_FACE_")
|
|
559
|
+
_FORWARD_EXACT = {"MAX_STEPS", ..., "MODEL_ID"}
|
|
560
|
+
|
|
561
|
+
-- so a developer with HF_TOKEN exported sends environment={"HF_TOKEN": ...} on a
|
|
562
|
+
restart, the guard short-circuits, and MODEL_ID/MAX_STEPS never carry. Observed
|
|
563
|
+
live: a resumed leg loaded the script's default model and printed
|
|
564
|
+
"save_steps: 10 (from args) != 20 (from trainer_state.json)".
|
|
565
|
+
|
|
566
|
+
body-wins is right for a SCALAR constraint (max_cost_usd); it is wrong for a MAP.
|
|
567
|
+
One unrelated forwarded var must not discard every other key. So: inherited keys
|
|
568
|
+
fill gaps, and any key the body actually sent wins.
|
|
569
|
+
"""
|
|
570
|
+
# The key under `environment` that holds the encrypted blob when queue_state was
|
|
571
|
+
# persisted with credentials encrypted at rest. Imported here (not module scope)
|
|
572
|
+
# to keep cred_at_rest off the import path of callers that never resume.
|
|
573
|
+
from shared.cred_at_rest import _ENV_SENTINEL
|
|
574
|
+
|
|
575
|
+
_incoming = getattr(body, "environment", None)
|
|
576
|
+
incoming = dict(_incoming) if isinstance(_incoming, dict) else {}
|
|
577
|
+
for prior in resume_chain:
|
|
578
|
+
state = _resolve_prior_submit_state(prior)
|
|
579
|
+
if state.get("byoc_cleanup_pending"):
|
|
580
|
+
# Its stored env was REPLACED with AWS termination-confirmation creds
|
|
581
|
+
# only (job_worker._byoc_cleanup_queue_state_for_unconfirmed_aws). It is
|
|
582
|
+
# non-empty but carries none of the user's --env, so stopping here would
|
|
583
|
+
# inherit a stripped snapshot instead of walking to a complete ancestor.
|
|
584
|
+
continue
|
|
585
|
+
env = state.get("environment")
|
|
586
|
+
if not isinstance(env, dict) or not env:
|
|
587
|
+
continue
|
|
588
|
+
# Defense in depth. _resolve_prior_submit_state decrypts the DB fallback, so
|
|
589
|
+
# this should not trigger -- but the encrypted shape is str->str and would
|
|
590
|
+
# sail through the sanitizer below, handing the resumed leg
|
|
591
|
+
# {"__vlenc_env__": "VLENC1:..."} instead of MODEL_ID/HF_TOKEN. If decryption
|
|
592
|
+
# is ever unavailable (key rotated away, legacy row), skip the leg and keep
|
|
593
|
+
# walking rather than inherit a sentinel.
|
|
594
|
+
if _ENV_SENTINEL in env:
|
|
595
|
+
continue
|
|
596
|
+
# A legacy or hand-edited queue_state could hold a shape no live submit can
|
|
597
|
+
# produce. Keep str->str only; never 500 on inherited data.
|
|
598
|
+
clean = {k: v for k, v in env.items()
|
|
599
|
+
if isinstance(k, str) and k and isinstance(v, str)}
|
|
600
|
+
if not clean:
|
|
601
|
+
continue
|
|
602
|
+
if not any(k not in incoming for k in clean):
|
|
603
|
+
return False # ancestor found, but it adds nothing new
|
|
604
|
+
# Body wins per key: a var the caller explicitly sent (or that the CLI
|
|
605
|
+
# forwarded from the shell) overrides the stored value for that key only.
|
|
606
|
+
body.environment = {**clean, **incoming}
|
|
607
|
+
return True
|
|
608
|
+
return False
|
|
609
|
+
|
|
610
|
+
|
|
520
611
|
def _inherit_constraints_from_chain(body, resume_chain: list[str]) -> list[str]:
|
|
521
612
|
"""Fill unset routing/budget constraints on a resume submit from the chain.
|
|
522
613
|
|
|
@@ -1546,6 +1637,19 @@ async def submit_job(body: RunJobRequest, user: dict = Depends(require_auth)):
|
|
|
1546
1637
|
# `vl restart --no-carry` sets inherit_constraints=False to resume with
|
|
1547
1638
|
# fresh (unconstrained) routing — the escape hatch when a carried
|
|
1548
1639
|
# constraint no longer fits.
|
|
1640
|
+
#
|
|
1641
|
+
# The environment carry is NOT gated on inherit_constraints: --no-carry drops
|
|
1642
|
+
# stale ROUTING/BUDGET constraints, but dropping the env would strip MODEL_ID
|
|
1643
|
+
# and HF_TOKEN and break the run outright. Without this, a resume leg ran with
|
|
1644
|
+
# an EMPTY env, so a script's `os.environ.get("MODEL_ID", <default>)` silently
|
|
1645
|
+
# loaded a different model and resumed from the prior model's checkpoint.
|
|
1646
|
+
if body.resume_from_job_id and _resume_chain:
|
|
1647
|
+
if _inherit_environment_from_chain(body, _resume_chain):
|
|
1648
|
+
logger.info(
|
|
1649
|
+
"[submit] resume leg merged environment from prior leg(s) %s "
|
|
1650
|
+
"(%d vars total; keys only, never values -- HF_TOKEN lives here)",
|
|
1651
|
+
_resume_chain, len(body.environment),
|
|
1652
|
+
)
|
|
1549
1653
|
if body.resume_from_job_id and _resume_chain and body.inherit_constraints:
|
|
1550
1654
|
_inherited_constraints = _inherit_constraints_from_chain(body, _resume_chain)
|
|
1551
1655
|
if "preferred_providers" in _inherited_constraints:
|
|
@@ -210,6 +210,143 @@ _HF_TRAINER_CLASSES = frozenset({"Trainer", "Seq2SeqTrainer"}) | _TRL_TRAINER_CL
|
|
|
210
210
|
_TRAINER_MODULES = ("transformers", "trl")
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
# Launcher-driven frameworks: the user hands over a config and the launcher starts
|
|
214
|
+
# the real trainer in a SUBPROCESS. There is no user training loop to instrument,
|
|
215
|
+
# so the AST injector correctly refuses -- but it then tells the user to "add the
|
|
216
|
+
# manual 3-line integration", which is impossible: an Axolotl YAML has nowhere to
|
|
217
|
+
# put restore()/checkpoint(). Mirrors the broker-side launcher set that decides
|
|
218
|
+
# whether to ship the resume hook (agents/broker/startup_scripts.py).
|
|
219
|
+
_FRAMEWORK_LAUNCHERS = frozenset({
|
|
220
|
+
"axolotl", "accelerate", "deepspeed", "torchrun", "llamafactory-cli",
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
# Of the launchers above, ONLY these own the training loop themselves: the user
|
|
224
|
+
# supplies a YAML/config and the framework runs (and checkpoints) the Trainer. A
|
|
225
|
+
# zero_code verdict is safe for them because there is no user loop to instrument.
|
|
226
|
+
#
|
|
227
|
+
# torchrun / accelerate / deepspeed are GENERIC process launchers. They own
|
|
228
|
+
# nothing -- `torchrun train.py` may front a raw PyTorch loop that still needs the
|
|
229
|
+
# injector. Classifying those zero_code told the user "checkpointing handled by
|
|
230
|
+
# VaultLayer" and skipped injection, so NO checkpoints were ever written and a
|
|
231
|
+
# preemption lost the whole run. The _has_injectable_loop() guard did not save it:
|
|
232
|
+
# a loop whose optimizer.step() sits in a helper function has no .step()/.backward()
|
|
233
|
+
# in the for-body, so the guard reads False. A generic launcher must fall through to
|
|
234
|
+
# _detect_hf_trl_trainer / Lightning (which already classify `accelerate launch
|
|
235
|
+
# train.py` correctly when train.py uses HF Trainer) and otherwise stay "missing",
|
|
236
|
+
# where the user is at least WARNED that integration is absent.
|
|
237
|
+
#
|
|
238
|
+
# NOTE: the broker's launcher set (agents/broker/startup_scripts.py) deliberately
|
|
239
|
+
# stays wider -- shipping the resume hook to a generic launcher is a harmless
|
|
240
|
+
# superset. The two sets are not interchangeable; do not "consolidate" them.
|
|
241
|
+
_LAUNCHER_OWNS_TRAINING_LOOP = frozenset({"axolotl", "llamafactory-cli"})
|
|
242
|
+
# `-m` modules whose ROOT package is too broad to match on ("torch").
|
|
243
|
+
_FRAMEWORK_LAUNCH_MODULES = {
|
|
244
|
+
"torch.distributed.run": "torchrun",
|
|
245
|
+
"torch.distributed.launch": "torchrun",
|
|
246
|
+
}
|
|
247
|
+
_LAUNCHER_DISPLAY = {
|
|
248
|
+
"axolotl": "Axolotl", "accelerate": "Accelerate", "deepspeed": "DeepSpeed",
|
|
249
|
+
"torchrun": "torchrun", "llamafactory-cli": "LLaMA-Factory",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _command_launcher_name(command) -> str:
|
|
254
|
+
"""Launcher name if ``command`` invokes one, else "". Matches the executable
|
|
255
|
+
basename, a ``-m`` module root, and the full ``-m`` path for torch.distributed.*.
|
|
256
|
+
Flags are skipped, so ``python train.py --deepspeed ds.json`` does not match,
|
|
257
|
+
and a script merely NAMED after a launcher (``python axolotl_test.py``) does not
|
|
258
|
+
either -- it is an argument, not the executable."""
|
|
259
|
+
expect_module = False
|
|
260
|
+
for tok in command or ():
|
|
261
|
+
if not isinstance(tok, str):
|
|
262
|
+
continue
|
|
263
|
+
if expect_module:
|
|
264
|
+
expect_module = False
|
|
265
|
+
if tok in _FRAMEWORK_LAUNCH_MODULES:
|
|
266
|
+
return _FRAMEWORK_LAUNCH_MODULES[tok]
|
|
267
|
+
root = tok.split(".", 1)[0]
|
|
268
|
+
if root in _FRAMEWORK_LAUNCHERS:
|
|
269
|
+
return root
|
|
270
|
+
continue
|
|
271
|
+
if tok == "-m":
|
|
272
|
+
expect_module = True
|
|
273
|
+
continue
|
|
274
|
+
if tok.startswith("-"):
|
|
275
|
+
continue
|
|
276
|
+
name = tok.replace("\\", "/").rsplit("/", 1)[-1]
|
|
277
|
+
if name in _FRAMEWORK_LAUNCHERS:
|
|
278
|
+
return name
|
|
279
|
+
return ""
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _script_subprocess_launcher(src: str) -> str:
|
|
283
|
+
"""Launcher name if the script shells out to one (``subprocess.call(["axolotl",
|
|
284
|
+
"train", cfg])``), else "". Covers the wrapper pattern, where the command is a
|
|
285
|
+
plain ``python wrapper.py`` and the launcher only appears at runtime.
|
|
286
|
+
|
|
287
|
+
Delegates the whole literal argv to _command_launcher_name rather than testing
|
|
288
|
+
argv[0]: the launcher is not always the executable. `subprocess.call(["python",
|
|
289
|
+
"-m", "torch.distributed.run", ...])` puts it in the `-m` module, and
|
|
290
|
+
`[sys.executable, "-m", ...]` hides argv[0] behind a non-literal entirely.
|
|
291
|
+
Non-literal elements are dropped, so a `-m` whose module is a variable simply
|
|
292
|
+
does not match.
|
|
293
|
+
|
|
294
|
+
Both subprocess arg shapes are handled: a literal argv list, and the string form
|
|
295
|
+
used with shell=True (`subprocess.run("axolotl train cfg.yml", shell=True)`),
|
|
296
|
+
which is shlex-split first -- otherwise it arrives as one unsplit token whose
|
|
297
|
+
"basename" is the entire command line and can never match a launcher.
|
|
298
|
+
"""
|
|
299
|
+
import ast
|
|
300
|
+
import shlex
|
|
301
|
+
try:
|
|
302
|
+
tree = ast.parse(src)
|
|
303
|
+
except SyntaxError:
|
|
304
|
+
return ""
|
|
305
|
+
for node in ast.walk(tree):
|
|
306
|
+
if not isinstance(node, ast.Call) or not node.args:
|
|
307
|
+
continue
|
|
308
|
+
fn = node.func
|
|
309
|
+
if not (isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name)
|
|
310
|
+
and fn.value.id == "subprocess"):
|
|
311
|
+
continue
|
|
312
|
+
arg0 = node.args[0]
|
|
313
|
+
if isinstance(arg0, ast.Constant) and isinstance(arg0.value, str):
|
|
314
|
+
try:
|
|
315
|
+
argv = shlex.split(arg0.value)
|
|
316
|
+
except ValueError: # unbalanced quotes -- degrade, never raise
|
|
317
|
+
argv = arg0.value.split()
|
|
318
|
+
else:
|
|
319
|
+
elts = arg0.elts if isinstance(arg0, (ast.List, ast.Tuple)) else [arg0]
|
|
320
|
+
argv = [e.value for e in elts
|
|
321
|
+
if isinstance(e, ast.Constant) and isinstance(e.value, str)]
|
|
322
|
+
name = _command_launcher_name(argv)
|
|
323
|
+
if name:
|
|
324
|
+
return name
|
|
325
|
+
return ""
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _has_injectable_loop(src: str) -> bool:
|
|
329
|
+
"""True if the script owns a training loop the AST injector can instrument.
|
|
330
|
+
|
|
331
|
+
Load-bearing guard: `vl run torchrun train.py` names a launcher but train.py may
|
|
332
|
+
be a raw-PyTorch loop that still NEEDS injection. Classifying it zero_code would
|
|
333
|
+
silently strip its checkpointing -- worse than a misleading message.
|
|
334
|
+
"""
|
|
335
|
+
import ast
|
|
336
|
+
try:
|
|
337
|
+
tree = ast.parse(src)
|
|
338
|
+
except SyntaxError:
|
|
339
|
+
return False
|
|
340
|
+
for node in ast.walk(tree):
|
|
341
|
+
if not isinstance(node, (ast.For, ast.While)):
|
|
342
|
+
continue
|
|
343
|
+
for sub in ast.walk(node):
|
|
344
|
+
if (isinstance(sub, ast.Call) and isinstance(sub.func, ast.Attribute)
|
|
345
|
+
and sub.func.attr in ("step", "backward")):
|
|
346
|
+
return True
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
|
|
213
350
|
def _detect_hf_trl_trainer(src: str) -> "tuple[bool, bool]":
|
|
214
351
|
"""``(uses_trainer, is_trl)`` from AST — REAL Trainer imports/instantiation.
|
|
215
352
|
|
|
@@ -461,7 +598,16 @@ def check_script_integration(command: list[str]) -> dict:
|
|
|
461
598
|
script_path = pathlib.Path(token)
|
|
462
599
|
break
|
|
463
600
|
|
|
601
|
+
# `axolotl train cfg.yml` carries no .py for us to scan, but the framework CLI
|
|
602
|
+
# itself proves a framework owns the loop and its checkpointing. A GENERIC
|
|
603
|
+
# launcher proves nothing (see _LAUNCHER_OWNS_TRAINING_LOOP) -- `torchrun
|
|
604
|
+
# train.py` with an unresolvable path stays "unknown", as it did before.
|
|
605
|
+
_cmd_launcher = _command_launcher_name(command)
|
|
606
|
+
|
|
464
607
|
if script_path is None or not script_path.exists():
|
|
608
|
+
if _cmd_launcher in _LAUNCHER_OWNS_TRAINING_LOOP:
|
|
609
|
+
result["status"] = "zero_code"
|
|
610
|
+
result["framework"] = _LAUNCHER_DISPLAY.get(_cmd_launcher, _cmd_launcher)
|
|
465
611
|
return result
|
|
466
612
|
|
|
467
613
|
result["script"] = script_path.name
|
|
@@ -532,6 +678,17 @@ def check_script_integration(command: list[str]) -> dict:
|
|
|
532
678
|
)
|
|
533
679
|
return result
|
|
534
680
|
|
|
681
|
+
# Framework-CLI driven: either the command IS one, or the script shells out to
|
|
682
|
+
# one. Restricted to _LAUNCHER_OWNS_TRAINING_LOOP -- a generic launcher over a
|
|
683
|
+
# raw PyTorch loop must reach the injector (or its warning), never a silent
|
|
684
|
+
# zero_code. _has_injectable_loop stays as defense in depth: if a script somehow
|
|
685
|
+
# owns a loop AND shells out to a framework CLI, prefer instrumenting the loop.
|
|
686
|
+
_launcher = _cmd_launcher or _script_subprocess_launcher(src)
|
|
687
|
+
if _launcher in _LAUNCHER_OWNS_TRAINING_LOOP and not _has_injectable_loop(src):
|
|
688
|
+
result["status"] = "zero_code"
|
|
689
|
+
result["framework"] = _LAUNCHER_DISPLAY.get(_launcher, _launcher)
|
|
690
|
+
return result
|
|
691
|
+
|
|
535
692
|
if "import jax" in lines or "import flax" in lines or "from flax" in lines:
|
|
536
693
|
result["status"] = "missing"
|
|
537
694
|
result["framework"] = "JAX/Flax"
|
|
@@ -754,14 +911,29 @@ def print_preflight(run_cmd: "RunCommand", yes: bool = False, train_mode: str =
|
|
|
754
911
|
(G, Y, R, B, BO),
|
|
755
912
|
)
|
|
756
913
|
elif _hint:
|
|
757
|
-
#
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
)
|
|
914
|
+
# The framework (HF Trainer / Lightning) already saves checkpoints — VaultLayer
|
|
915
|
+
# syncs them to R2 and, on single-node, auto-resumes from the last one with NO
|
|
916
|
+
# code change (the runtime hook resolves the checkpoint from output_dir even when
|
|
917
|
+
# VAULTLAYER_RESUME_CHECKPOINT is unset). Do NOT tell the user to add a resume
|
|
918
|
+
# line there — the old "(for resume: trainer.train(resume_from_checkpoint=os.
|
|
919
|
+
# environ.get(...)))" hint was a footgun: on a restart/local leg that env var is
|
|
920
|
+
# None, which trained fresh AND suppressed the auto-resume hook. Multi-node does
|
|
921
|
+
# NOT arm the hook (it is _SINGLE_NODE-gated), so there the explicit hand-off IS
|
|
922
|
+
# still required — surface it only in that case.
|
|
923
|
+
_nnodes = int(getattr(run_cmd, "world_nnodes", 1) or 1)
|
|
924
|
+
if _nnodes > 1:
|
|
925
|
+
_example = integration.get("resume_example") or (
|
|
926
|
+
f"os.environ.get('{_hint}')"
|
|
927
|
+
)
|
|
928
|
+
integ_line = (
|
|
929
|
+
f"{G}{_fw} — checkpoints auto-saved ✓{R} "
|
|
930
|
+
f"{Y}(multi-node resume: {_example}){R}"
|
|
931
|
+
)
|
|
932
|
+
else:
|
|
933
|
+
integ_line = (
|
|
934
|
+
f"{G}{_fw} — checkpoints auto-saved ✓{R} "
|
|
935
|
+
f"{Y}(VaultLayer resumes from your last saved checkpoint automatically){R}"
|
|
936
|
+
)
|
|
765
937
|
else:
|
|
766
938
|
integ_line = f"{G}{_fw} — zero-code ✓{R}"
|
|
767
939
|
elif _st == "missing":
|
|
@@ -221,13 +221,19 @@ _ALLOWED_REMOTE_MODULE_LAUNCHERS = {
|
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
def _normalize_bare_python_script_command(command: list[str]) -> list[str]:
|
|
224
|
-
"""Run bare local Python scripts through the interpreter
|
|
225
|
-
|
|
226
|
-
Users naturally type `vl run train.py`.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
224
|
+
"""Run bare local Python scripts through the interpreter.
|
|
225
|
+
|
|
226
|
+
Users naturally type `vl run train.py`. Neither end of the run can execute
|
|
227
|
+
that directly: the GPU node receives the script as a regular uploaded file,
|
|
228
|
+
not an executable with a guaranteed shebang/chmod, and the local-execution
|
|
229
|
+
path spawns argv via create_subprocess_exec. Both need `python train.py`.
|
|
230
|
+
Explicit launchers such as `python -u train.py`, `torchrun ... train.py`,
|
|
231
|
+
and `accelerate launch ...` are already intentional and must remain
|
|
232
|
+
unchanged.
|
|
233
|
+
|
|
234
|
+
Applied at RunCommand construction (cli/run.py), so callers downstream
|
|
235
|
+
already see a normalized command. Idempotent — re-normalizing a command
|
|
236
|
+
that already starts with an interpreter is a no-op.
|
|
231
237
|
"""
|
|
232
238
|
if not command:
|
|
233
239
|
return []
|
|
@@ -478,11 +484,11 @@ async def run_remote(
|
|
|
478
484
|
raise SystemExit(
|
|
479
485
|
f"vaultlayer run: script file not found: {py_args[0]}\n"
|
|
480
486
|
"Run from the directory containing the script, or pass the path, "
|
|
481
|
-
f"for example: vaultlayer run
|
|
487
|
+
f"for example: vaultlayer run path/to/{Path(py_args[0]).name}"
|
|
482
488
|
)
|
|
483
489
|
raise SystemExit(
|
|
484
490
|
"vaultlayer run: managed jobs require one local .py training script, "
|
|
485
|
-
"for example: vaultlayer run
|
|
491
|
+
"for example: vaultlayer run train.py"
|
|
486
492
|
)
|
|
487
493
|
_workspace_paths = _local_paths_requiring_workspace(command, script_arg_index)
|
|
488
494
|
if _workspace_paths:
|
|
@@ -1173,8 +1179,8 @@ async def run_remote(
|
|
|
1173
1179
|
print(f"\n ⚠ No {gpu_str} found after 15 minutes.")
|
|
1174
1180
|
print(f" Options:")
|
|
1175
1181
|
print(f" • Press Ctrl+C to cancel (no charge) and retry with a different GPU:")
|
|
1176
|
-
print(f" vaultlayer run --gpu A100_40GB
|
|
1177
|
-
print(f" vaultlayer run --gpu A100_80GB
|
|
1182
|
+
print(f" vaultlayer run --gpu A100_40GB train.py ...")
|
|
1183
|
+
print(f" vaultlayer run --gpu A100_80GB train.py ...")
|
|
1178
1184
|
print(f" • Keep waiting — job will start automatically if capacity opens\n")
|
|
1179
1185
|
_queue_warned = True
|
|
1180
1186
|
sys.stdout.flush()
|
|
@@ -696,7 +696,7 @@ def connect_aws(role_arn, external_id, access_key, secret_key, region):
|
|
|
696
696
|
_append_to_config_env(config_env, "AWS_REGION", region or "us-east-1")
|
|
697
697
|
|
|
698
698
|
click.echo(" AWS connected.")
|
|
699
|
-
click.echo(" Run: vaultlayer run
|
|
699
|
+
click.echo(" Run: vaultlayer run train.py")
|
|
700
700
|
|
|
701
701
|
|
|
702
702
|
@connect.command(name="gcp")
|
|
@@ -809,7 +809,7 @@ def connect_gcp(service_account_json, project_id, region):
|
|
|
809
809
|
_append_to_config_env(config_env, "GCP_REGION", region)
|
|
810
810
|
|
|
811
811
|
click.echo(" GCP connected.")
|
|
812
|
-
click.echo(" Run: vaultlayer run
|
|
812
|
+
click.echo(" Run: vaultlayer run train.py")
|
|
813
813
|
|
|
814
814
|
|
|
815
815
|
@connect.command(name="azure")
|
|
@@ -926,7 +926,7 @@ def connect_azure(subscription_id, tenant_id, client_id, client_secret, resource
|
|
|
926
926
|
_append_to_config_env(config_env, "AZURE_LOCATION", location)
|
|
927
927
|
|
|
928
928
|
click.echo(" Azure connected.")
|
|
929
|
-
click.echo(" Run: vaultlayer run
|
|
929
|
+
click.echo(" Run: vaultlayer run train.py")
|
|
930
930
|
|
|
931
931
|
|
|
932
932
|
@connect.command(name="storage")
|
|
@@ -995,7 +995,7 @@ def connect_storage(provider):
|
|
|
995
995
|
_append_to_config_env(config_env, "VL_S3_BUCKET", bucket)
|
|
996
996
|
|
|
997
997
|
click.echo(" S3 storage connected.")
|
|
998
|
-
click.echo(" Run: vaultlayer run
|
|
998
|
+
click.echo(" Run: vaultlayer run train.py")
|
|
999
999
|
|
|
1000
1000
|
elif provider == "gcs":
|
|
1001
1001
|
click.echo("")
|
|
@@ -94,7 +94,7 @@ def datasets(ctx, delete):
|
|
|
94
94
|
Upload a dataset:
|
|
95
95
|
vaultlayer sync ./data --dataset-id my-dataset
|
|
96
96
|
Use in training:
|
|
97
|
-
vaultlayer run --data r2://my-dataset
|
|
97
|
+
vaultlayer run --data r2://my-dataset train.py
|
|
98
98
|
Delete a dataset:
|
|
99
99
|
vaultlayer datasets delete <dataset-id>
|
|
100
100
|
"""
|
|
@@ -228,9 +228,9 @@ def regions_list_all():
|
|
|
228
228
|
click.echo(f" {code:<20} {note}")
|
|
229
229
|
|
|
230
230
|
click.echo("\n To restrict your job to specific regions:")
|
|
231
|
-
click.echo(" vaultlayer run --regions eu-central-1,eu-west-1
|
|
231
|
+
click.echo(" vaultlayer run --regions eu-central-1,eu-west-1 train.py")
|
|
232
232
|
click.echo(" To exclude a region:")
|
|
233
|
-
click.echo(" vaultlayer run --excluded-regions cn-north-1
|
|
233
|
+
click.echo(" vaultlayer run --excluded-regions cn-north-1 train.py\n")
|
|
234
234
|
|
|
235
235
|
|
|
236
236
|
@regions.command(name="current")
|
|
@@ -405,7 +405,7 @@ def estimate(script, gpu, hours, gpus, data, data_gb, checkpoint_gb, model_param
|
|
|
405
405
|
else:
|
|
406
406
|
click.echo(f" (band spans model sizes — larger models run longer; pass --model-params for a size-specific band)")
|
|
407
407
|
|
|
408
|
-
click.echo(f" Run: vaultlayer run --gpu {gpu}
|
|
408
|
+
click.echo(f" Run: vaultlayer run --gpu {gpu} train.py")
|
|
409
409
|
click.echo("")
|
|
410
410
|
|
|
411
411
|
|