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": { ... }
}
| Field | Meaning |
|---|---|
version | Plan schema version. "1" today. |
root | Which component to invoke run.run() on. Always "python". |
linkage | "runtime" — composectl resolves bindings at instantiation time. "static" requires pre-composed artifacts. |
components | Array of component records. Each has an id and a digest (32-byte sha256 as a JSON array of integers). |
bindings | Array of (consumer_id, import_name) → (provider_id, export_name) records. Every non-wasi import declared by python.wasm needs one entry. |
policy | determinism + capabilities grants for runtime-resolved features. |
components
{ "id": "aead", "digest": [112, 58, 92, 115, 245, 100, 148, 237, ...] }
id— matches the[[cap]]id incaps-manifest.tomland the cap id inbuild-pylon-dynlink-plan.py'sCAPSlist.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:invokegate 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).