Skip to main content

Plan JSON structure

plans/python-pylon-dynlink.json is the composectl Plan that pylon-launch reads at startup. It describes the component graph (root, bindings, dynlink providers) and the digests of every component. The file is auto-generated by scripts/build-pylon-dynlink-plan.py.

Regenerate

Run whenever caps are added, removed, or a sibling .wasm moves on disk:

python3 scripts/build-pylon-dynlink-plan.py

The script walks every non-wasi import declared by python.wasm, locates the corresponding cap component .wasm on disk (using the same env-var defaults as scripts/compose-python-component.sh), stages each blob under .compose/blobs/, and writes the JSON.

Caps that aren't present on disk are SKIPPED with a warning — the corresponding imports go unsatisfied; running python.wasm will trap at instantiation. The script prints a manifest at the end so you know what to build to complete the plan.

Top-level shape

{
"version": "1",
"root": "python",
"linkage": "runtime",
"components": [ ... ],
"bindings": [ ... ],
"policy": { ... }
}
FieldMeaning
versionPlan schema version. "1" today.
rootWhich component to invoke run.run() on. Always "python".
linkage"runtime" — composectl resolves bindings at instantiation time. "static" requires pre-composed artifacts.
componentsArray of component records. Each has an id and a digest (32-byte sha256 as a JSON array of integers).
bindingsArray of (consumer_id, import_name)(provider_id, export_name) records. Every non-wasi import declared by python.wasm needs one entry.
policydeterminism + capabilities grants for runtime-resolved features.

components

{ "id": "aead", "digest": [112, 58, 92, 115, 245, 100, 148, 237, ...] }
  • id — matches the [[cap]] id in caps-manifest.toml and the cap id in build-pylon-dynlink-plan.py's CAPS list.
  • digest — 32-byte sha256 of the blob in .compose/blobs/, encoded as a JSON array of unsigned bytes. Pylon-launch trusts each digest before handing it to composectl.

bindings

{
"consumer_id": "python",
"import_name": "arrow:core/array@0.1.0",
"provider_id": "arrow-core",
"export_name": "arrow:core/array@0.1.0"
}

Every non-wasi import declared by python.wasm needs one entry naming which component satisfies it. wac-graph matches on (consumer_id, import_name).

policy

{
"determinism": "relaxed",
"capabilities": [
{ "name": "dynlink:resolve", "level": "required" },
{ "name": "dynlink:invoke", "level": "required" }
]
}
  • determinism"relaxed" allows the host to grant wall-clock, random, and network capabilities. "strict" denies them.
  • capabilities — grants for runtime-resolved features. dynlink:resolve / dynlink:invoke gate the runtime-dynlink path; without them, _crc32c_dynlink_cap (and any other dynlink-shaped cap) fails at import.

Invocation

pylon-launch plans/python-pylon-dynlink.json -c "..."

The launcher trusts each component digest via the embedded API (workaround for the missing composectl trust add CLI).