Skip to content

Python consumer

scrollcase_consumer mirrors the local Node consumer without depending on Node or its CLI:

sh
python -m pip install scrollcase-consumer
python
from scrollcase_consumer import (
    attach_extracted_box,
    run_box,
    run_extracted_box,
    verify_and_extract_box,
    verify_extracted_payload,
)

prepared = verify_and_extract_box(
    "release.json",
    public_key_path="trusted-keys.json",
    archive="box.zip",
    destination="/srv/boxes/example-1.0.0",
)

result = run_extracted_box(
    prepared,
    args=("--port", "8080"),
    env={"APPLICATION_MODE": "local"},
)

The receipt fields use idiomatic snake case (box_id, target_id, required_assets, archive_sha256, environment_report). attach_extracted_box(release, public_key_path=…, root=…) and verify_extracted_payload(release, public_key_path=…, root=…) mirror their Node counterparts exactly, including the attached status and the refusal of a release that commits to no payload digest. run_box performs the same one-shot prepare/run/cleanup composition. Stream arguments accept Python file objects or subprocess constants; the default inherits the parent's streams. On the main Python thread, SIGINT, SIGTERM, and SIGHUP are forwarded and then the previous handlers are restored.

EnvironmentReport, EnvironmentVariableReport, and EnvironmentSourceValue are immutable public models. Their fields mirror the Node structure in snake case; BoxRunResult and every verification receipt include one.

The rest of the public models are the receipts and the box description they carry, all frozen dataclasses:

ModelWhat it is
PreparedBoxThe receipt verify_and_extract_box and attach_extracted_box return. It is not a plain record: execution authority is bound to the exact instance, so a field-identical copy is refused
PayloadVerificationWhat verify_extracted_payload returns — status, root, identity, and the number of entries checked
BoxRunResultThe child application's terminal result: exit_code, signal, and a report
RequiredAssetOne deferred asset the caller must materialize before execution, with the url, size_bytes and sha256 the release signed, and executable when the scroll declared the bit
BoxTargetplatform, arch, accelerator, and cuda_version where it applies
BoxRuntimeWhat runs inside the box: id, and the version and entry_point a runtime with an interpreter has
BoxExecutionThe union of the four entry-point shapes — PythonScriptExecution, PythonModuleExecution, NodeScriptExecution, NativeBinaryExecution. None on a library-only box

All three runtimes — python, node and native — are implemented here. A box naming an id this release has no adapter for is refused by name, never misread as the runtime its paths resemble.

Every operation that verifies a signed release takes public_key_path or trusted_keys, exactly one, and parse_trusted_keys(source) reads both trust-file shapes from text or bytes — so an application holding its keys in a keyring, an environment variable or a secrets manager verifies against them directly instead of writing key material to a file first. Naming both sources or neither raises a ScrollcaseConsumerError; the Rust TrustAnchors enum makes those two invalid states unrepresentable instead.

The distribution is not a downloader: callers still supply local release, archive, trust-key, destination, and on-demand asset paths. It verifies Ed25519 signatures with cryptography and validates bundled, generated copies of the canonical schemas.