letify 2.5.1__tar.gz → 2.5.2__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.
- {letify-2.5.1 → letify-2.5.2}/PKG-INFO +1 -1
- {letify-2.5.1 → letify-2.5.2}/docs/SPEC.md +9 -1
- {letify-2.5.1 → letify-2.5.2}/letify/__init__.py +1 -1
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/worker.py +22 -4
- {letify-2.5.1 → letify-2.5.2}/letify/providers/kaggle_adapter.py +10 -4
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/client.py +17 -3
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/channel.py +2 -2
- {letify-2.5.1 → letify-2.5.2}/letify-core/Cargo.lock +3 -3
- {letify-2.5.1 → letify-2.5.2}/letify-core/Cargo.toml +1 -1
- {letify-2.5.1 → letify-2.5.2}/letify-ext/package-lock.json +2 -2
- {letify-2.5.1 → letify-2.5.2}/letify-ext/package.json +1 -1
- {letify-2.5.1 → letify-2.5.2}/pyproject.toml +1 -1
- {letify-2.5.1 → letify-2.5.2}/tests/fake_kaggle_adapter.py +3 -7
- {letify-2.5.1 → letify-2.5.2}/tests/test_kaggle.py +45 -2
- {letify-2.5.1 → letify-2.5.2}/tests/test_protocol.py +2 -2
- {letify-2.5.1 → letify-2.5.2}/tests/test_providers.py +2 -2
- {letify-2.5.1 → letify-2.5.2}/.github/workflows/ci.yml +0 -0
- {letify-2.5.1 → letify-2.5.2}/.github/workflows/docs.yml +0 -0
- {letify-2.5.1 → letify-2.5.2}/.github/workflows/publish.yml +0 -0
- {letify-2.5.1 → letify-2.5.2}/.gitignore +0 -0
- {letify-2.5.1 → letify-2.5.2}/.researchtree +0 -0
- {letify-2.5.1 → letify-2.5.2}/CLAUDE.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/LICENSE +0 -0
- {letify-2.5.1 → letify-2.5.2}/PROJECT.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/COMPONENT.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/INTENT.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/NETWORK.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/01-getting-started.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/02-providers.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/03-execution-modes.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/04-environments-and-data.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/05-concurrency.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/06-cost.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/07-troubleshooting.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/08-releasing.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/02-/355/224/204/353/241/234/353/260/224/354/235/264/353/215/224.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/04-/355/231/230/352/262/275/352/263/274-/353/215/260/354/235/264/355/204/260.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/06-/353/271/204/354/232/251.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/08-/353/246/264/353/246/254/354/212/244.md" +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/guide/ko/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/locales/README_ko.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/docs/mkdocs.yml +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/.letify.example +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/00_smoke.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/01_sweep.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/02_resume.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/03_watch.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/README_ko.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/common.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/examples/nvfp4-lora/recipe.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/cli.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/inventory.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/login.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/schema.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/secrets.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/config/writer.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/cache.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/env.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/function.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/instance.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/reduce.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/declare/replicas.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/errors.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/install.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/launcher.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/paths.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/codec.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/driver.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/handle.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/protocol/wire.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/base.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/colab.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/colab_files.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/colab_keepalive.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/elice.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/kaggle.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/local.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/modal.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/modal_adapter.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/naming.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/shell.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/tunnel.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/providers/usage.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/capability.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/cuda.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/executor.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/frames.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/guard.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/tensor.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/trace.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/device/value.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/loader.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/remoting/probe.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/render.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/bootstrap.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/broker.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/lease.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/pool.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/session.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/runtime/telemetry.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/backends/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/backends/filesystem.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/backends/google_auth.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/backends/layout.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/backends/objects.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/cas.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/pathdata.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/sendorder.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/store/volume.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/stubs.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/tools.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/__init__.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/agent.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/announce.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/link.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/nat.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/pipeline.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/portmap.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/probe.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/quic.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/rendezvous.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/setup.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/sshopts.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify/transport/strategies.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/.gitignore +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/build.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-agent/Cargo.toml +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-agent/src/driver.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-agent/src/main.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/Cargo.toml +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/client.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/exports.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/image.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/lib.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/status.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-driver/src/table.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-quic/Cargo.toml +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-quic/src/main.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-wire/Cargo.toml +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-wire/src/lib.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/letify-wire/tests/throughput.rs +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-core/rust-toolchain.toml +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/.vscodeignore +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/LICENSE +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/README.md +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/media/icon.svg +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/cli.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/extension.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/history.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/model.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/poll.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/src/render.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/test/cli.test.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/test/manifest.test.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/test/model.test.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/test/regressions.test.ts +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify-ext/tsconfig.json +0 -0
- {letify-2.5.1 → letify-2.5.2}/letify_providers.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/scripts/sync-main.sh +0 -0
- {letify-2.5.1 → letify-2.5.2}/scripts/version.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/conftest.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/fake_modal_adapter.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_cli.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_cli_output.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_colab_keepalive.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_config.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_core.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_data.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_data_streaming.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_declare.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_device.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_distributed.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_editor_sessions.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_elice.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_frames.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_install.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_kaggle_cookie.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_kaggle_link.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_link_log.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_notebook.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_parallel_placement.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_paths.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_portmap.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_quic.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_remoting.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_render.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_runtime.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_ship.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_store.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_strategies.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_stubs.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_transport.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_tunnel_setup.py +0 -0
- {letify-2.5.1 → letify-2.5.2}/tests/test_version.py +0 -0
|
@@ -656,7 +656,15 @@ A **persistent channel** keeps one worker process alive behind a pipe pair. Mess
|
|
|
656
656
|
|
|
657
657
|
A **one-shot channel** can only run a command and collect its output. Every call starts a fresh process, so nothing persists. It exists because some transports offer nothing more, and it refuses the operations that need persistence rather than pretending.
|
|
658
658
|
|
|
659
|
-
The worker source cannot be sent on standard input as a script, because `python -` reads to end of file before compiling anything and the pipe has to stay open for requests. A small bootstrap stub passed with `-c` reads one line holding a decimal byte count from `sys.stdin.buffer`, reads that many bytes of UTF-8 worker source, executes them, and leaves standard input where it was. From then on both pipes carry frames only. The
|
|
659
|
+
The worker source cannot be sent on standard input as a script, because `python -` reads to end of file before compiling anything and the pipe has to stay open for requests. A small bootstrap stub passed with `-c` reads one line holding a decimal byte count from `sys.stdin.buffer`, reads that many bytes of UTF-8 worker source, executes them, and leaves standard input where it was. From then on both pipes carry frames only. The count line is `<byte count> <digest>`, where the digest is the first 16 bytes of the source's
|
|
660
|
+
blake2b in hexadecimal, from `hashlib` so the stub needs nothing installed. The stub exits rather
|
|
661
|
+
than compiling when either disagrees: `the letify worker source arrived short: <read> of <expected>
|
|
662
|
+
bytes`, or `the letify worker source arrived corrupted: <read> bytes, digest <found>, expected
|
|
663
|
+
<wanted>`. A program cut or shifted anywhere fails as a `SyntaxError` at whatever construct the
|
|
664
|
+
damage landed inside, which names the symptom and hides the cause. The count alone is not enough,
|
|
665
|
+
because a transport that loses one run of bytes and gains another of the same length keeps the count
|
|
666
|
+
intact: a Kaggle session reported an unterminated triple-quoted string at a line that holds no quote
|
|
667
|
+
in the source, which is what a shifted source looks like and what a digest catches.
|
|
660
668
|
|
|
661
669
|
### Frames <!-- id: frames -->
|
|
662
670
|
|
|
@@ -37,17 +37,35 @@ from . import wire as _wire
|
|
|
37
37
|
#: enough to survive shell quoting reads a byte count line and that many bytes of source,
|
|
38
38
|
#: execs them, and leaves standard input where it was.
|
|
39
39
|
BOOTSTRAP = (
|
|
40
|
-
"import sys;"
|
|
40
|
+
"import sys,hashlib;"
|
|
41
41
|
"b=sys.stdin.buffer;"
|
|
42
|
-
"
|
|
42
|
+
"h=b.readline().split();"
|
|
43
|
+
"n=int(h[0]);"
|
|
43
44
|
"s=b.read(n);"
|
|
44
|
-
# Spec "Channels": a source cut anywhere compiles as whatever construct the
|
|
45
|
-
# inside, or runs and exits without serving
|
|
45
|
+
# Spec "Channels": a source cut or shifted anywhere compiles as whatever construct the
|
|
46
|
+
# damage landed inside, or runs and exits without serving. The count catches a cut and
|
|
47
|
+
# the digest catches a shift, which keeps the count intact.
|
|
46
48
|
"len(s)==n or sys.exit("
|
|
47
49
|
"'the letify worker source arrived short: %d of %d bytes'%(len(s),n));"
|
|
50
|
+
"d=hashlib.blake2b(s,digest_size=16).hexdigest();"
|
|
51
|
+
"d==h[1].decode() or sys.exit("
|
|
52
|
+
"'the letify worker source arrived corrupted: %d bytes, digest %s, expected %s'"
|
|
53
|
+
"%(len(s),d,h[1].decode()));"
|
|
48
54
|
"exec(compile(s.decode(),'letify-worker','exec'))"
|
|
49
55
|
)
|
|
50
56
|
|
|
57
|
+
def count_line(body: bytes) -> bytes:
|
|
58
|
+
"""The line the bootstrap stub reads before the source: its length and its digest.
|
|
59
|
+
|
|
60
|
+
Spec "Channels": the digest is the first 16 bytes of blake2b in hexadecimal, from
|
|
61
|
+
``hashlib`` so the stub needs nothing installed.
|
|
62
|
+
"""
|
|
63
|
+
import hashlib
|
|
64
|
+
|
|
65
|
+
digest = hashlib.blake2b(body, digest_size=16).hexdigest()
|
|
66
|
+
return b"%d %s\n" % (len(body), digest.encode("ascii"))
|
|
67
|
+
|
|
68
|
+
|
|
51
69
|
_BODY = r'''
|
|
52
70
|
import hashlib, io, queue, sys, tarfile, time, traceback
|
|
53
71
|
|
|
@@ -31,14 +31,20 @@ UNREACHABLE = 4
|
|
|
31
31
|
#: The stub the shim starts the worker with, the one Channels describes: a byte count line,
|
|
32
32
|
#: then that many bytes of source.
|
|
33
33
|
STUB = (
|
|
34
|
-
"import sys;"
|
|
34
|
+
"import sys,hashlib;"
|
|
35
35
|
"b=sys.stdin.buffer;"
|
|
36
|
-
"
|
|
36
|
+
"h=b.readline().split();"
|
|
37
|
+
"n=int(h[0]);"
|
|
37
38
|
"s=b.read(n);"
|
|
38
|
-
# Spec "Channels": a source cut anywhere compiles as whatever construct the
|
|
39
|
-
# inside, or runs and exits without serving
|
|
39
|
+
# Spec "Channels": a source cut or shifted anywhere compiles as whatever construct the
|
|
40
|
+
# damage landed inside, or runs and exits without serving. The count catches a cut and
|
|
41
|
+
# the digest catches a shift, which keeps the count intact.
|
|
40
42
|
"len(s)==n or sys.exit("
|
|
41
43
|
"'the letify worker source arrived short: %d of %d bytes'%(len(s),n));"
|
|
44
|
+
"d=hashlib.blake2b(s,digest_size=16).hexdigest();"
|
|
45
|
+
"d==h[1].decode() or sys.exit("
|
|
46
|
+
"'the letify worker source arrived corrupted: %d bytes, digest %s, expected %s'"
|
|
47
|
+
"%(len(s),d,h[1].decode()));"
|
|
42
48
|
"exec(compile(s.decode(),'letify-worker','exec'))"
|
|
43
49
|
)
|
|
44
50
|
|
|
@@ -99,9 +99,21 @@ class Pending:
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
#: Passed to ``python -c`` to start the worker: read a length-prefixed source, execute it.
|
|
102
|
+
#: Spec "Channels": the count catches a cut and the digest catches a shift, which keeps the
|
|
103
|
+
#: count intact, so neither failure reaches the compiler as a syntax error.
|
|
102
104
|
BOOTSTRAP = (
|
|
103
|
-
"import sys;
|
|
104
|
-
"
|
|
105
|
+
"import sys,hashlib;"
|
|
106
|
+
"b=sys.stdin.buffer;"
|
|
107
|
+
"h=b.readline().split();"
|
|
108
|
+
"n=int(h[0]);"
|
|
109
|
+
"s=b.read(n);"
|
|
110
|
+
"len(s)==n or sys.exit("
|
|
111
|
+
"'the letify device source arrived short: %d of %d bytes'%(len(s),n));"
|
|
112
|
+
"d=hashlib.blake2b(s,digest_size=16).hexdigest();"
|
|
113
|
+
"d==h[1].decode() or sys.exit("
|
|
114
|
+
"'the letify device source arrived corrupted: %d bytes, digest %s, expected %s'"
|
|
115
|
+
"%(len(s),d,h[1].decode()));"
|
|
116
|
+
"exec(compile(s,'letify-device','exec'))"
|
|
105
117
|
)
|
|
106
118
|
|
|
107
119
|
|
|
@@ -1089,7 +1101,9 @@ def connect(
|
|
|
1089
1101
|
target=client._drain_stderr, args=(process.stderr,), name=f"{name}-stderr", daemon=True
|
|
1090
1102
|
).start()
|
|
1091
1103
|
try:
|
|
1092
|
-
|
|
1104
|
+
from ...protocol.worker import count_line
|
|
1105
|
+
|
|
1106
|
+
transport.raw(count_line(source) + source)
|
|
1093
1107
|
client.start()
|
|
1094
1108
|
except (TransportClosed, RuntimeLost) as exc:
|
|
1095
1109
|
process.kill()
|
|
@@ -654,14 +654,14 @@ class FramedChannel(Channel):
|
|
|
654
654
|
|
|
655
655
|
def _send_worker(self) -> None:
|
|
656
656
|
"""Hand the worker source to the bootstrap stub: a byte count line, then the source."""
|
|
657
|
-
from ..protocol.worker import source
|
|
657
|
+
from ..protocol.worker import count_line, source
|
|
658
658
|
|
|
659
659
|
connection = self._connection
|
|
660
660
|
assert connection is not None
|
|
661
661
|
data = source(text_frames=self.text_frames).encode("utf-8")
|
|
662
662
|
connection.hello = None
|
|
663
663
|
try:
|
|
664
|
-
connection.sender.raw(
|
|
664
|
+
connection.sender.raw(count_line(data) + data)
|
|
665
665
|
except OSError:
|
|
666
666
|
# The worker is already gone. Waiting for its hello reports why.
|
|
667
667
|
pass
|
|
@@ -138,7 +138,7 @@ dependencies = [
|
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "letify-agent"
|
|
141
|
-
version = "2.5.
|
|
141
|
+
version = "2.5.2"
|
|
142
142
|
dependencies = [
|
|
143
143
|
"letify-wire",
|
|
144
144
|
"libloading",
|
|
@@ -146,7 +146,7 @@ dependencies = [
|
|
|
146
146
|
|
|
147
147
|
[[package]]
|
|
148
148
|
name = "letify-driver"
|
|
149
|
-
version = "2.5.
|
|
149
|
+
version = "2.5.2"
|
|
150
150
|
dependencies = [
|
|
151
151
|
"letify-wire",
|
|
152
152
|
]
|
|
@@ -164,7 +164,7 @@ dependencies = [
|
|
|
164
164
|
|
|
165
165
|
[[package]]
|
|
166
166
|
name = "letify-wire"
|
|
167
|
-
version = "2.5.
|
|
167
|
+
version = "2.5.2"
|
|
168
168
|
|
|
169
169
|
[[package]]
|
|
170
170
|
name = "libc"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letify-status",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "letify-status",
|
|
9
|
-
"version": "2.5.
|
|
9
|
+
"version": "2.5.2",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^18.19.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "letify-status",
|
|
3
3
|
"displayName": "letify Status",
|
|
4
4
|
"description": "letify quota and GPU activity in the status bar, read from the letify CLI.",
|
|
5
|
-
"version": "2.5.
|
|
5
|
+
"version": "2.5.2",
|
|
6
6
|
"publisher": "darkpyonix",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
8
8
|
"repository": {
|
|
@@ -63,13 +63,9 @@ except (urllib.error.URLError, OSError) as exc:
|
|
|
63
63
|
# The cell a real bridge executes carries a shim, because a kernel answers input() and not a
|
|
64
64
|
# binary read. Here the same shape is a subprocess whose standard input and output are pipes:
|
|
65
65
|
# the bridge copies lines in and out, exactly as it would copy them to input_request replies
|
|
66
|
-
# and from stream messages.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"b=sys.stdin.buffer;"
|
|
70
|
-
"n=int(b.readline());"
|
|
71
|
-
"exec(compile(b.read(n).decode(),'letify-worker','exec'))"
|
|
72
|
-
)
|
|
66
|
+
# and from stream messages. The stub is the real one rather than a copy: a copy of it drifted
|
|
67
|
+
# from the real stub when the count line gained a digest, and the drift read as a dead worker.
|
|
68
|
+
from letify.providers.kaggle_adapter import STUB # noqa: E402
|
|
73
69
|
|
|
74
70
|
worker = subprocess.Popen(
|
|
75
71
|
[sys.executable, "-u", "-c", STUB],
|
|
@@ -1660,13 +1660,13 @@ def test_the_stub_refuses_a_worker_source_that_arrived_short() -> None:
|
|
|
1660
1660
|
"""
|
|
1661
1661
|
import subprocess
|
|
1662
1662
|
|
|
1663
|
-
from letify.protocol.worker import BOOTSTRAP, source
|
|
1663
|
+
from letify.protocol.worker import BOOTSTRAP, count_line, source
|
|
1664
1664
|
|
|
1665
1665
|
body = source().encode("utf-8")
|
|
1666
1666
|
# The count says the whole source; the pipe carries all but the last 284 bytes.
|
|
1667
1667
|
done = subprocess.run(
|
|
1668
1668
|
[sys.executable, "-u", "-c", BOOTSTRAP],
|
|
1669
|
-
input=
|
|
1669
|
+
input=count_line(body) + body[:-284],
|
|
1670
1670
|
capture_output=True,
|
|
1671
1671
|
timeout=120,
|
|
1672
1672
|
)
|
|
@@ -1716,3 +1716,46 @@ def test_a_kernel_line_stays_inside_the_limit_a_kernel_carries() -> None:
|
|
|
1716
1716
|
assert len(source().encode("utf-8")) > KaggleChannel.WRITE_LIMIT, (
|
|
1717
1717
|
"the source must cross as several lines, or this pins nothing"
|
|
1718
1718
|
)
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
def test_the_stub_refuses_a_worker_source_whose_bytes_were_shifted() -> None:
|
|
1722
|
+
"""Spec "Channels": the count alone misses a loss and a gain of the same length.
|
|
1723
|
+
|
|
1724
|
+
KICT's Kaggle session reported an unterminated triple-quoted string at generated line
|
|
1725
|
+
3077, which holds `job = _JOBS.get()` in that build and no quote at all, so the source
|
|
1726
|
+
was shifted rather than cut. A shift keeps the byte count intact.
|
|
1727
|
+
"""
|
|
1728
|
+
import subprocess
|
|
1729
|
+
|
|
1730
|
+
from letify.protocol.worker import BOOTSTRAP, count_line, source
|
|
1731
|
+
|
|
1732
|
+
body = source().encode("utf-8")
|
|
1733
|
+
# Four bytes lost from the middle and four gained at the end: the same length.
|
|
1734
|
+
shifted = body[:50_000] + body[50_004:] + b"\n" * 4
|
|
1735
|
+
assert len(shifted) == len(body)
|
|
1736
|
+
done = subprocess.run(
|
|
1737
|
+
[sys.executable, "-u", "-c", BOOTSTRAP],
|
|
1738
|
+
input=count_line(body) + shifted,
|
|
1739
|
+
capture_output=True,
|
|
1740
|
+
timeout=120,
|
|
1741
|
+
)
|
|
1742
|
+
assert b"arrived corrupted" in done.stderr, done.stderr[-2000:]
|
|
1743
|
+
assert b"SyntaxError" not in done.stderr
|
|
1744
|
+
|
|
1745
|
+
|
|
1746
|
+
def test_a_worker_source_that_arrives_whole_still_runs() -> None:
|
|
1747
|
+
"""Spec "Channels": the check costs the good case nothing, so the worker still starts."""
|
|
1748
|
+
import subprocess
|
|
1749
|
+
|
|
1750
|
+
from letify.protocol.worker import BOOTSTRAP, count_line, source
|
|
1751
|
+
|
|
1752
|
+
body = source().encode("utf-8")
|
|
1753
|
+
done = subprocess.run(
|
|
1754
|
+
[sys.executable, "-u", "-c", BOOTSTRAP],
|
|
1755
|
+
input=count_line(body) + body,
|
|
1756
|
+
capture_output=True,
|
|
1757
|
+
timeout=120,
|
|
1758
|
+
)
|
|
1759
|
+
# The worker announces itself and then reads, so closing its input ends it cleanly.
|
|
1760
|
+
assert b"arrived" not in done.stderr, done.stderr[-2000:]
|
|
1761
|
+
assert done.stdout, "the worker said nothing, so it never started"
|
|
@@ -187,10 +187,10 @@ def test_a_worker_call_whose_body_imports_letify_while_running_names_the_reason(
|
|
|
187
187
|
|
|
188
188
|
return inside.session_cache("value", lambda: 1)
|
|
189
189
|
|
|
190
|
-
from letify.protocol.worker import SOURCE
|
|
190
|
+
from letify.protocol.worker import SOURCE, count_line
|
|
191
191
|
|
|
192
192
|
source = SOURCE.encode()
|
|
193
|
-
stdin = bytearray(
|
|
193
|
+
stdin = bytearray(count_line(source) + source)
|
|
194
194
|
sender = wire.Sender(lambda view: stdin.extend(view) or view.nbytes)
|
|
195
195
|
head, buffers = codec.dumps_call_parts(work, (), {})
|
|
196
196
|
sender.message(wire.REQUEST, 1, {"op": "call", "payload": head, "buffers": buffers})
|
|
@@ -1457,7 +1457,7 @@ def test_a_sandbox_channel_sends_the_worker_once_and_then_framed_requests(
|
|
|
1457
1457
|
) -> None:
|
|
1458
1458
|
import base64
|
|
1459
1459
|
|
|
1460
|
-
from letify.protocol.worker import SOURCE
|
|
1460
|
+
from letify.protocol.worker import SOURCE, count_line
|
|
1461
1461
|
from letify.protocol.worker import source as worker_source
|
|
1462
1462
|
|
|
1463
1463
|
provider = provider_of(Modal, "m")
|
|
@@ -1471,7 +1471,7 @@ def test_a_sandbox_channel_sends_the_worker_once_and_then_framed_requests(
|
|
|
1471
1471
|
written = b"".join(base64.b64decode(r["data"]) for r in fake_modal.requests("write"))
|
|
1472
1472
|
# The worker source went out exactly once, behind its byte count line, with the flag
|
|
1473
1473
|
# that makes the sandbox write its frames as base64 lines.
|
|
1474
|
-
assert written.count(
|
|
1474
|
+
assert written.count(count_line(source) + source) == 1
|
|
1475
1475
|
assert SOURCE.encode() not in written
|
|
1476
1476
|
finally:
|
|
1477
1477
|
provider.stop(runtime)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{letify-2.5.1 → letify-2.5.2}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.5.1 → letify-2.5.2}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.5.1 → letify-2.5.2}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.5.1 → letify-2.5.2}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|