Adopting azphalt as an asset host
How an app becomes a conforming azphalt host for asset packages only — no code execution. ADOPTION.md covers the full host that runs code extensions in a sandbox and renders their UI; this is the lighter profile for an app that just wants to consume portable assets (LUTs, shaders, transitions, brushes). Guillotine, the local-first video editor, is the exemplar: it adopts azphalt purely to consume .cube LUTs, ISF/GLSL shaders, and gl-transitions as portable .azp packages.
Why this profile exists
An asset-kind .azp is pure data — normalized asset bytes, a manifest, integrity digests, and an optional host-rendered UI panel. It carries no executable code, so consuming it needs none of the runtime machinery a code host requires:
| A full (code) host needs… | An asset host needs… |
|---|---|
| A WASM sandbox (QuickJS-in-WASM) | — |
| A capability grant model + host-function table | — |
| The image-buffer ABI / shared memory | — |
| To enforce the never-list against running code | — |
| Verify + read + apply the asset with its own engine | ✔ (only this) |
That is exactly why a video editor — whose engine is nothing like a paint host's — can adopt the standard: it never touches azphalt's editor surface, only its packages.
What an asset host does
Open and verify. Treat the
.azpas a ZIP; runverifyAzp(@azphalt/azp). It confirms safe paths, every payload digest, payload completeness, and — if asignature.jsonis present — the Ed25519 signature. Refuse anything that fails. (A signature alone is tamper-evidence, not identity; check the signer against a trust store withverifyTrustwhere provenance matters — seespec/package-format.md § Signing.)Read the manifest. Reject packages where
kindiscode. Forkind: "mixed", ignore the root-level code fields (entry,runtime) and process onlymanifest.assets— this profile runs no code. A pureassetpackage has only assets. In amixedpackage, skip any asset markedstandalone: false— that asset is generated or completed by the code you're not running, so installing it would give a broken/empty result; apply only assets wherestandalone !== false(spec/extension-manifest.md§ Mixed-package asset independence).Apply each asset with your own engine, dispatched by
type.spec/extension-manifest.md§assetsis the single source of truth for thetypevocabulary (and the SDKAssetTypeunion is its machine-readable form) — apply the ones your engine understands and skip the rest (see Unknown types below). The full0.1set:- Traditional:
brush·lut·pattern·stamp·shader·transition·mesh·material·hdri·motion·palette·image·video·font·audio·vector·template·overlay. - AI models:
tflite·litert·onnx·sherpa-bundle.
The ones with importers + a pinned wire format today:
lut— a.cubecolor transform → your color pipeline (Guillotine: theapply_lutpath).shader— GLSL/ISF source (params.format=isf|glsl) → compile and run as a per-clip effect; the declared inputs are inparamsand (for user controls) theuipanel.transition— a gl-transition GLSL (params.format=gl-transition) → your transition engine;from/to/progress/ratioare host-provided, the rest are user controls.brush/pattern/stamp— PNG data → your brush/pattern engine (paint hosts).
Asset metadata your dispatch should read (all defined in
spec/extension-manifest.md§assets):role— an optional semantic label on a model asset (e.g.role: "depth","speech-to-text") that routes a generic model graph to the correct host engine slot. Route byrole, not by guessing from the file.byteSize— an advisory size (bytes), so a host can budget storage/allocate before fetching a large asset.- The remote-asset pattern — for a file too large to bundle (multi-hundred-MB models), the author sets
path: ""(the key stays present) and providesremoteUrl+checksum(SHA-256) +byteSize. Treat an emptypathas "not bundled": fetch fromremoteUrlwith your own (resumable) download manager, then verify the downloaded bytes againstchecksumbefore use and reject on mismatch — the remote analog of thefilesdigest gate.
Unknown types. The
typevocabulary is open and grows across spec versions, so an asset host WILL meet atypeit doesn't recognize. Parse-and-skip it — ignore the unrecognized asset and apply the ones you do support; do not reject the whole package. A package pairing alutyou understand with, say, a futureholo-widgetyou don't should still install its LUT. (@azphalt/conformance'sunknown-type-skipcheck certifies exactly this.)- Traditional:
Render the UI panel, if present. When an asset has a
uipath (e.g.ui/grade.json), parse it as aPanel(spec/ui-schema.md) and draw the controls with your own toolkit — the same declarative slider/toggle/select/color schema a Compose or SwiftUI host renders. Feed the values back into the asset's parameters. Do not render extension HTML; there is none.
Conformance checklist (asset host)
Check your host against @azphalt/conformance — runAssetConformance(host) drives your host's load through these lines and returns a pass/fail report — rather than against prose.
- [ ] Opens the
.azp, runsverifyAzp, and refuses any package that fails integrity (or, when signed, signature) verification. - [ ] Reads
manifest.assetsand applies each supportedtypewith its own engine. - [ ] Parses-and-skips an unrecognized asset
type— applies the types it supports and ignores the rest, rather than rejecting the whole package (unknown-type-skip). - [ ] For a remote asset (
path: ""+remoteUrl+checksum), fetches the bytes and verifies them againstchecksumbefore use, rejecting on mismatch. - [ ] Runs no code from a package (and rejects
kind: "code"packages entirely). - [ ] Renders an asset's
uipanel natively from the UI schema and round-trips its parameters. - [ ] Validates the package's
compatfield against its own supported specification version.
Meet these and any conforming asset .azp — a LUT built once, a shader, a transition — runs in your app, and the same package works in every other adopting app. That is the portability promise, for data-only hosts.
Importers seed the catalog
The asset packages an asset host consumes are produced by the importers in this repo: @azphalt/importer-cube (LUTs), @azphalt/importer-isf (shaders), and @azphalt/importer-transition (gl-transitions) — so the worldwide libraries of these formats become portable .azp packages a video host (or a paint host) can load unchanged.