JigDAW Host and Plugin Contract
Version: 0.1.0-draft
Status: normative. An implementation claiming conformance follows this document exactly.
Host identifier string: jigdaw-host/0.1.0
Requirement keywords (MUST, MUST NOT, SHOULD, SHOULD NOT, MAY) are used in the RFC 2119 sense.
This document specifies what a JigDAW host guarantees and what a Jig must do in return. A Jig is a plugin conforming to this specification. JigDAW is the specification. It is the web-native equivalent of a plugin API such as VST3 or LV2, and it assumes the Web Audio API and WebAssembly rather than a native binary interface.
The vocabulary the profile is written in is vocabs/jigdaw.ttl. The profile format is
described in plugin-profiles.md. The messages exchanged between host,
processor and user interface are specified in messaging.md, and latency
compensation in latency.md. Where this document and the vocabulary disagree,
this document governs and the vocabulary is a defect.
1. Identity and discovery
1.1 A plugin is a URL
A plugin MUST be identified by an absolute https: IRI. That IRI MUST be dereferenceable
and MUST return the plugin's profile. There is no separate registry, no identifier scheme
and no install step: the IRI is the name, the metadata is what the name returns, and
installation is having fetched it.
http: IRIs MUST NOT be used, except on loopback, where http://localhost,
http://127.0.0.1 and http://[::1] MUST be accepted.
Away from loopback, a page serving a JigDAW host is served over TLS and a browser refuses
mixed content, so an http: plugin is unreachable in practice as well as unsafe in
principle.
The loopback exception is not a relaxation. A browser already treats http://localhost as a
secure context, because it cannot be intercepted, and grants it every other secure-context
feature for that reason. Refusing it would mean the only way to develop a plugin is to deploy
it, which is the opposite of what a local host is for. This was found by running the host on
localhost and watching it refuse its own plugins.
Rationale, since a registry is the reflex and will be proposed again: a registry makes the registry's operator the arbiter of what exists. Dereferenceable IRIs mean a plugin author publishes by publishing, and a catalogue becomes one opinion about what is worth finding rather than the precondition for being found at all.
1.2 What the IRI returns
A plugin IRI MUST support content negotiation and MUST serve:
Accept |
Response |
|---|---|
text/turtle |
the profile as Turtle |
application/ld+json |
the profile as JSON-LD |
text/html |
a page a person can read |
text/html MUST be the default when no Accept header expresses a preference, so that
pasting a plugin IRI into a browser shows something useful.
A host MUST send Accept: text/turtle, application/ld+json;q=0.9. A host MUST accept a
profile served as text/plain, which is what most static hosts and GitHub's raw view
return for a .ttl file, and MUST determine the syntax by examining the content rather
than by trusting the media type.
1.3 CORS is mandatory
A plugin's profile, processor, module, user interface and every declared asset MUST be
served with Access-Control-Allow-Origin permitting the host's origin, whether by * or
by explicit echo.
This is not a recommendation that can be relaxed. AudioWorklet.addModule() fetches
cross-origin in CORS mode; a response without the header is not merely untrusted, it is
unreadable. A plugin without CORS cannot be loaded by any host on any origin but its own,
and a host MUST report that as a hosting defect naming the missing header, not as a
generic load failure.
1.4 Caching
A host SHOULD cache a profile and its resources by IRI, and MUST revalidate according to ordinary HTTP cache semantics. A host MUST NOT treat a profile as immutable unless the response says so. A plugin author changing a plugin's behaviour without changing its IRI MUST change its resources' integrity digests, which invalidates the host's cached copy by construction.
2. Capability negotiation
2.1 Answered before anything is fetched
A plugin declares what it needs with trn:requires, and what it can use but does without
with jig:prefers. A host MUST evaluate every trn:requires before fetching any code. If
any required capability is unavailable, the host MUST NOT fetch the module and MUST report
which capability was missing.
Rationale: the alternative is to load and instantiate a plugin, then discover it cannot work. That wastes a download, runs untrusted code for no reason, and produces a failure whose cause is several steps removed from the thing that caused it.
A module's jig:wasmFeature values are requirements in the same sense, evaluated at the
same point. A host MUST NOT fetch a module declaring a WebAssembly feature its engine does
not support, and MUST report which feature was missing. A browser host SHOULD find out by
asking the engine to validate a minimal module using the feature, rather than by the
browser's name or version. Without this, the same missing feature surfaces as a compile
error from inside the worklet that names an opcode rather than a cause.
2.2 Capabilities
| Capability | Meaning |
|---|---|
trn:HostTransport |
The host provides tempo and beat position |
jig:MidiEvents |
The host delivers and accepts MIDI |
jig:SharedMemory |
SharedArrayBuffer is available |
jig:CrossOriginIsolation |
The page is cross-origin isolated |
jig:OfflineRender |
The plugin may run faster than real time |
jig:Persistence |
The host saves and restores plugin state |
A host MUST pass the resolved set of available capabilities to the processor in its
processorOptions at construction, so a plugin that declared jig:prefers can choose its
fallback without probing.
2.3 Shared memory is negotiated, never assumed
SharedArrayBuffer requires the page to be cross-origin isolated, which requires
Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp
on the host document. Under require-corp, every cross-origin subresource must opt in
with Cross-Origin-Resource-Policy: cross-origin or it fails to load.
A host therefore MUST NOT require cross-origin isolation of itself as a baseline. Doing so
would impose Cross-Origin-Resource-Policy on every server hosting any plugin anywhere,
which contradicts section 1.1: a plugin author would publish by publishing and then find
that publishing was not enough.
A host MAY offer an isolated mode, in which case it MUST offer jig:SharedMemory only
there, and plugins requiring it are available only in that mode. A plugin SHOULD prefer
rather than require shared memory, and SHOULD fall back to transferring buffers over its
message port.
3. Instantiation
3.1 Order
A host MUST perform the following in order, and MUST abort at the first failure:
- Fetch the profile. Parse it. Validate it against
vocabs/shapes.ttl. - Evaluate
trn:requires(section 2.1). - Resolve
jig:locationon each resource against the profile's base IRI. - Fetch the module and the processor, verifying each against its
jig:integrity. await audioContext.audioWorklet.addModule(processorUrl).- Construct an
AudioWorkletNodeusing the processor'sjig:registeredName, withnumberOfInputs,numberOfOutputsandoutputChannelCounttaken from the profile. - Post the module's bytes to the processor and await its ready message (section 3.3).
- Only then connect the node into the graph.
A plugin MUST NOT be connected into the audio graph before step 7 completes. A processor whose WebAssembly instance is not ready MUST output silence, MUST NOT throw, and MUST NOT be audible.
3.2 Integrity is verified, not declared
A host MUST verify every fetched resource against the jig:integrity digest in the
profile before executing or instantiating it, and MUST refuse the plugin on mismatch.
For the processor module, a host SHOULD use the integrity option of addModule() where
the browser supports it. Where it does not, the host MUST fetch the resource itself, verify
the digest with crypto.subtle.digest, and pass a blob URL to addModule().
A profile that omits jig:integrity on any resource is invalid (vocabs/shapes.ttl) and
MUST be refused. A host MUST NOT offer an option to skip verification. The profile and the
code it names need not share an origin, and an unverified profile is an instruction to
execute whatever currently sits at a URL.
3.2a A module MAY declare a portable ABI
The processor is JavaScript, so only a browser can run a plugin through it. A module MAY
additionally declare jig:abi, and a host with a WebAssembly runtime and no JavaScript
engine MAY then load the module directly and ignore the processor entirely.
This is optional for a plugin and optional for a host. A browser host SHOULD continue to use the processor, which is the plugin author's own code and may do more than the ABI exposes. See module-abi.md.
3.3 WebAssembly is compiled inside the worklet
The host MUST post the module's bytes, as an ArrayBuffer, and SHOULD transfer rather than
copy them. The processor MUST compile them synchronously with new WebAssembly.Module(bytes)
and instantiate with new WebAssembly.Instance(module), and MUST post a ready message once
its instance and all its buffers exist.
A host MUST NOT post a compiled WebAssembly.Module to an AudioWorklet. An earlier
version of this document required exactly that, on the reasoning that a Module carries
already-compiled code and would keep compilation off the audio thread. It does not work.
Measured in Chrome on 2026-09-17: port.postMessage({ module }) does not throw, and the
message is never delivered. The processor waits for an init that never arrives and the load
fails ten seconds later with a timeout that names nothing useful. A WebAssembly.Module is
serializable only within an agent cluster, and an AudioWorklet is outside the page's.
The synchronous compile is deliberate and is allowed here. The 4 KB limit on
new WebAssembly.Module() applies to the main thread, not to a worklet: a 227 KB module
compiles synchronously inside one without complaint, measured the same day.
The init and ready messages are specified in messaging.md section 1.
A host MAY call WebAssembly.validate() on the bytes before posting them, which is cheap and
gives a precise error naming the module rather than a generic instantiation failure from
inside the worklet.
A processor MUST NOT call fetch(), import() or any other network operation, and SHOULD
NOT await anything on the path to becoming ready. AudioWorkletGlobalScope deliberately has no fetch,
and a plugin discovering this at run time is a plugin that was written against the wrong
model.
3.4 Memory is allocated before the plugin is audible
A processor MUST allocate every buffer it will ever use before it posts its ready message:
its WebAssembly memory, its event queue, its scratch arrays. process() MUST NOT allocate.
WebAssembly.Memory.grow() MUST NOT be called from process(). It detaches every existing
ArrayBuffer view on that memory, so any Float32Array the processor is holding becomes
zero-length without warning, and the symptom is silence rather than an exception.
4. The process contract
4.1 Real-time rules
Inside process() a plugin MUST NOT:
- allocate, including creating any array, object, closure or string;
- call
WebAssembly.Memory.grow(); - touch the filesystem, the network or any storage API;
- log through an unbounded sink;
- take a lock whose hold time is not bounded;
- throw.
These are not style preferences. process() runs on a thread with a hard deadline of one
render quantum. Missing it produces an audible glitch, and a garbage collection pause
produces a run of them.
A plugin MUST communicate with everything outside the audio thread through preallocated buffers and bounded lock-free queues.
4.2 Shape
process(inputs, outputs, parameters) follows the Web Audio API. Additionally:
- A plugin MUST write every frame of every channel of every output it declared, writing zeroes where it has nothing to say. An untouched output buffer's contents are not specified to be silent.
- A plugin MUST NOT assume
inputs[n]has the channel count it declared. A disconnected input arrives as an empty array, and a plugin MUST treat that as silence rather than reading index 0 of nothing. - A plugin MUST NOT write to its input buffers.
- A plugin MUST return
truewhile it is producing or may yet produce output, and MAY returnfalseonly when it is permanently finished. A plugin with ajig:tailFramesgreater than zero MUST keep returningtruefor at least that many frames after its input falls silent.
Latency, tails and feedback are specified in latency.md.
4.3 Render quantum
The render quantum is 128 frames. A plugin MUST NOT hardcode that figure where the code
would be wrong if it changed; it MUST read outputs[0][0].length or the equivalent. A
plugin that cannot work at another quantum MUST declare jig:renderQuantum 128, so that a
host running at a different quantum refuses it rather than corrupting its buffers.
5. Parameters
5.1 One declaration
A parameter is declared once, as an lv2:port on the profile, with lv2:symbol,
lv2:name, lv2:default, lv2:minimum, lv2:maximum and optionally units:unit.
From that one declaration the host MUST derive both the processor's
parameterDescriptors and the control drawn on the generated panel. They cannot disagree,
because there is nothing for them to disagree about.
lv2:symbol becomes the AudioParam name. It is what automation and the saved project key
on, and a plugin MUST NOT change a symbol between versions without treating it as a new
parameter.
5.2 Automation rate
A port declaring jig:automationRate jig:ARate becomes an a-rate AudioParam; anything
else is k-rate. k-rate is the default, and a plugin SHOULD declare a-rate only for
parameters meant to be audio-modulated: an a-rate parameter costs a 128-element
Float32Array per parameter per quantum whether or not anything is modulating it.
A processor MUST handle a parameters[name] array of length 1, which is what Web Audio
passes when a value is constant across the quantum, as well as one of length 128.
5.3 Widgets follow shape
The control the host draws is determined by the shape of the declaration and never by the plugin naming a widget:
- a port with
lv2:portProperty lv2:toggled, or an enumeration with exactly twolv2:scalePoints, is a two-position switch; - an enumeration with more scale points is a selector with every option named;
- anything else is a dial.
An author wanting a switch declares a switch's shape. Rationale: a widget name in a profile
is a second source of truth about a port that the port already describes, and the two drift.
This rule is inherited from valis, where no view is permitted to test toggled itself.
5.4 Every position must mean something
A parameter position that collapses onto another for some settings of the plugin is worse than no position at all. A plugin SHOULD NOT declare a mode whose behaviour is identical to another mode under any reachable configuration.
5.5 A text asset is edited as text
A jig:asset marked jig:userReplaceable whose jig:mediaType is text/plain is drawn by the
host as a text area, beside the file chooser every replaceable asset has. Like section 5.3, the
shape of the declaration decides this and the plugin names no widget. It is how a plugin whose
input is words (a lyric, a scale file) lets a person type them, with no code of its own: the
generated panel is on every page and a jig:ui is not (section 9.1).
- The text is the asset's bytes, read and written as UTF-8. The host MUST show the text the
plugin has loaded, which is what a
stateRequestreturns under the asset's key (messaging.md section 1.3), not what it last sent. A plugin with a text asset MUST therefore return the asset's bytes under its key, as the plugin that loads any other asset does. - Applying sends the text exactly as a chosen file is sent, as
loadAsset. There is no second path to the processor. jig:maxBytes, where the profile declares it, is the size of the plugin's buffer. The host MUST state the limit beside the text in words, count it in bytes and not characters, and refuse to apply text over it, saying so. It MUST NOT send text the plugin will clip without telling the person. Where the profile declares none, the host states the size and sends what it was given.- Text a person has typed and not applied MUST NOT be replaced by text arriving from the plugin (a restored session, a replaced file): the host keeps the typing and says the plugin's text changed.
6. Events
6.1 MIDI is a host service
The Web Audio graph carries audio and nothing else. MIDI and other events reach a plugin
over the AudioWorkletNode's MessagePort, provided by the host. The wire format is
specified in messaging.md.
A plugin that accepts or produces any MIDI signal type MUST declare
trn:requires jig:MidiEvents. vocabs/shapes.ttl enforces this, because a plugin that
declares trn:produces trn:Midi and does not require the service is a plugin whose output
silently goes nowhere.
6.2 Events are located by stream position
Every event MUST carry an absolute stream position in frames, as a frame field, and a
processor MUST apply an event in the quantum that contains it, comparing by range:
if (event.frame >= blockStart && event.frame < blockStart + quantum)A plugin MUST NOT locate an event by its index within the current block, and MUST NOT test a stream position for equality against a block boundary.
Rationale, stated at length because this bug is written twice in valis's MISTAKES.md from
opposite directions: an event's offset within a block is meaningless once the block has
passed, and an event whose position is not exactly a multiple of the quantum is never equal
to a boundary and so never fires at all. Both failures are silent, intermittent, and
present as "the timing is slightly off" rather than as an error.
6.3 Ordering and bounds
The host MUST deliver events in non-decreasing frame order. The processor's event queue
MUST be bounded and preallocated, and on overflow the processor MUST drop events and report
the count on its next outgoing message. It MUST NOT grow the queue.
7. Transport
A plugin declaring trn:requires trn:HostTransport receives, for each quantum, the tempo
in BPM, the transport state, the position in beats at the start of the quantum, the time
signature, and the loop points where a loop is active.
A plugin MUST derive musical timing from the supplied beat position and MUST NOT derive it
by counting process() calls. Counting blocks desynchronises the moment the transport is
repositioned, looped or retempoed, and produces a plugin that is correct only while nothing
happens.
The host MUST advance the beat position continuously within a quantum where a plugin needs sub-quantum musical phase, by supplying the position at the start of the quantum and the beats per frame.
8. State
8.1 The contract comes before the editor
A plugin's state serialisation contract MUST be settled before any user interface work begins. A plugin MUST be able to load state written by any earlier version of itself, and MUST supply defaults for anything a stored state does not mention.
8.2 Shape
State MUST be a structured-cloneable JavaScript value, obtained from the plugin through its message port and restored through it. A plugin MUST NOT store state that only makes sense on the machine that produced it, such as a local file path.
Parameter values are not state: they are AudioParams and the host already saves them. A
plugin MUST NOT duplicate a parameter's value into its state, because on restore the two
disagree and it is not defined which wins.
9. User interface
9.1 Sandboxed
A plugin's user interface, where it is code, MUST be loaded into an iframe with a sandbox
attribute. A host MUST NOT load plugin UI code into its own document by any means. A skin
(section 9.4) is data and is the one thing a host MAY draw in its own document. The frame
MUST keep allow-scripts and allow-same-origin and MUST NOT grant anything else, because
messaging.md section 2.1 requires every message to be addressed to the
frame's real origin and forbids "*" in both directions.
An interface MAY be served from the host's own origin. A host that serves plugins itself must
be able to offer their editors, and a frame at all is better than no editor. It is still a
separate document, and postMessage in the protocol above is the only channel the host gives
it. It is not isolated, though: allow-same-origin on the host's origin runs that code with
the page's access, so the frame is a document boundary and not a security boundary. A host
that serves a plugin is vouching for it, and MUST NOT describe such a frame as isolated. A
plugin the host does not serve is on another origin, and there the sandbox does isolate it.
A plugin fetched from an origin the host does not control is code the host has no reason to trust. Any UI script running in the host's document could read the project, the user's storage, any credentials present and every other plugin, and could rewire the audio graph directly. The sandbox is what makes section 1.1 a reasonable thing to propose.
A plugin with no jig:skin gets a panel generated from its port declarations, whether or not it
has a jig:ui. This is the expected case, not a degraded one: a generated panel is consistent
with every other plugin, it is accessible, and it costs the author nothing. A jig:ui is an
editor offered beside the panel (section 9.4), because the panel is the one interface the host
knows to be accessible and a page the host did not write cannot be vouched for.
9.2 The UI never touches the graph
A user interface MUST communicate only by postMessage with the host, using the protocol in
messaging.md section 2. It MUST NOT be given a reference to the
AudioContext, the AudioWorkletNode, the processor's message port, or any part of the
project model.
Changes go from the UI, to the host's operation layer, to the model, to the compiled graph. A UI that mutates the running graph directly is a second implementation of every operation, and the two diverge. A host drawing a skin is under the same rule: its gestures become the same operations the generated panel issues.
9.3 One operation layer
The host's UI, its WebMCP surface and any other control surface MUST all be thin adapters over a single operation dispatcher. An operation MUST NOT be implemented twice.
9.4 Skins (optional)
A plugin MAY declare jig:skin: a Turtle document of control bindings that points at an SVG
artwork, specified in svg-skins.md. The panel a host draws for a plugin is its
skin where it has one and the skin loads, and a generated panel otherwise. A jig:ui, where
present, is an editor the host MAY offer beside the panel; it MUST NOT be the only way to reach
a port. A host MAY show the editor in place of the panel when a person asks it to, and MAY open the editor by default and keep the generated panel one control away, as Jiggy does. A host that
does not implement skins ignores jig:skin and generates the panel.
A skin is data. The sandbox in section 9.1 exists because UI code could read the host's storage and rewire the graph, and a skin has no code to do either. That holds only while the host keeps three rules, and a host that cannot keep them MUST draw the skin in a sandboxed frame or not at all.
- The host MUST NOT insert the artwork's markup into its document. It MUST parse the file as
image/svg+xmland build new nodes from an allowlist of elements, attributes and style properties, so that nothing the author wrote reaches the document except by being recognised and copied. - An element in the SVG namespace outside the allowlist, any script, event attribute,
foreignObject, animation element, or reference to anything but a fragment of the same file MUST make the host refuse the whole skin, with an error naming the element and its position in the tree, and fall back to the generated panel (section 10.2). - The host MUST give the control its role, name, value text, keyboard handling and focus indicator itself, taking the name and unit from the port declaration. The artwork supplies none of them.
A skin MUST NOT cause a port to become unreachable. The host draws every port the skin does not bind in a generated group beside it, and leaves out a binding to a port the profile does not declare.
A skin MAY instead be an HTML skin, specified in html-skins.md: one HTML file with one stylesheet and no
script, which the profile's jig:skin points at directly and whose jig:mediaType is text/html. The three rules above
hold for it, and the host MUST keep the further ones that its stylesheet needs. It MUST NOT hand the author's stylesheet to
the browser as text: it MUST parse it, check every rule and declaration against an allowlist that refuses url(),
@import, @font-face, !important, quotes and escapes, hiding properties and fixed or sticky positioning, and write a
new stylesheet from what it recognised. It MUST draw the skin in a shadow root that contains and isolates it, and its own
rules, which give every control a visible focus indicator and a target of at least 44 CSS pixels, MUST be outside the cascade
layer the author's rules are confined to, so that no author declaration can override them.
10. Errors
10.1 Located and recoverable
Every failure a host reports MUST name what failed, where, and what would fix it. A parse failure MUST carry a line and column. A validation failure MUST carry the focus node and the constraint. A capability failure MUST name the capability.
10.2 A failed plugin does not stop the music
Loading a plugin that fails at any step MUST leave the existing audio graph running and
unchanged. A plugin that throws in process() MUST be muted and disconnected by the host,
MUST be reported, and MUST NOT take down the context.
Plugin code is untrusted and imperfect. A host whose failure mode is silence for the whole project is a host nobody will load an unfamiliar plugin into, which would defeat the point.
10.3 Failure is a result
A host SHOULD record load outcomes as jig:Inspection records, including failures. Which
plugins work in which browsers is a fact about the ecosystem, and it is only discoverable
if unsuccessful loads are written down rather than merely reported and forgotten.
11. Security
A host MUST:
- serve itself over TLS, with a Content Security Policy that permits WebAssembly compilation and frames only the sandboxed plugin UI origin;
- verify every resource against its declared integrity digest (section 3.2);
- sandbox every plugin user interface (section 9.1), and never describe one it serves itself as isolated;
- treat every value arriving from a plugin, a profile or a catalogue as data and never as markup or as code.
A host MUST NOT:
- offer to skip integrity verification;
- execute a profile's contents in any way, including evaluating a string from one;
- grant a plugin access to the host's storage, credentials, or any capability not listed in section 2.2.
Profiles are fetched from arbitrary origins. A catalogue listing is data. A plugin's name, description and caution are strings written by someone else, and they reach a page.
12. Foreign plugins
A foreign plugin is a plugin in a format this contract does not define, loaded through an adapter. The worked case is a Web Audio Module, and nothing here is specific to it.
Support is OPTIONAL. A host that refuses every foreign plugin conforms fully, and refusing is the safe default.
12.1 What is given up, stated rather than relaxed
A native plugin never runs in the host's document. Its processor is an AudioWorklet, which
has no fetch and no DOM, and its interface is a separate document in a sandbox (section
9.1). That is what makes section 1.1 a reasonable thing to propose: install is a HTTP GET,
and the thing installed does not run in the page.
A foreign plugin's entry point is a JavaScript module the host imports into its own document and calls. It runs with the host's origin and the host's privileges. It can read the project, the host's storage, any credentials present and every other plugin, and it can rewire the audio graph directly. That is exactly what section 9.1 exists to prevent, and it is not preventable for a format whose entry point is a main-thread object.
So this section does not weaken sections 9.1 or 11. It defines a separate class that is outside them, and requires a host to say so.
A host MUST NOT describe a foreign plugin as verified, sandboxed or isolated. A host MUST NOT claim to enforce section 2.2 against one: capability negotiation is a promise a foreign plugin never made and the host cannot hold it to.
12.2 Declaration
A foreign plugin is declared as jig:ForeignPlugin and MUST NOT be declared jig:WebPlugin.
The two classes are disjoint so that no host can mistake one for the other, and so that the
shapes that govern a loadable native plugin never appear to have passed on a foreign one.
It MUST declare:
jig:foreignFormat, the format its adapter speaks;jig:container, a single archive carrying everything it needs, with ajig:integrity;jig:entryPoint, the path inside that archive the adapter starts at.
12.3 The container is what is verified
A host MUST fetch the container, verify it against its jig:integrity, and execute nothing
until that succeeds. There is no skip path, exactly as in section 3.2.
A host MUST serve the container's contents to the plugin from the bytes it verified, and MUST refuse any request within the space it serves that the container does not hold. The serving mechanism MUST NOT fall back to the network for such a request: a missing file is a refusal, never a fetch.
A host MUST NOT claim more than that. A request that leaves the space the host serves is an
ordinary request from the host's own document, because the plugin is running in it, and the
host does not mediate it. Measured: a path containing .. is normalised by the browser before
any service worker sees it, so it leaves the worker's scope and is never offered for refusal.
A plugin does not need .. to do this and can simply fetch an absolute path.
This replaces the enumeration section 3.2 relies on rather than abandoning it. A native plugin declares every file and each is verified; a foreign plugin declares one file and it is verified, and nothing else is reachable. Both answer the same question, which is whether the code about to run is the code that was published.
A host cannot fully enforce the boundary against a hostile plugin, because a foreign plugin running in the host's document can reach the network by means the host does not mediate. It MUST still impose the boundary, because it defeats substitution, which is the threat section 3.2 names, and because an ordinary plugin reaching outside its container is then visible. A host SHOULD record such attempts and MAY report them.
12.3a The adapter's own runtime
A foreign format may require host-side code in the host's own AudioWorklet before any of its
plugins can be instantiated. A Web Audio Module does: its WamEnv and a WamGroup are
injected by the host, are shared by every plugin in that context, and are not in any
container.
That code is part of the host, not part of a plugin. A host MUST obtain it the way it obtains its own code, MUST NOT fetch it from a plugin's origin, and MUST NOT allow a plugin to supply or replace it. A host SHOULD pin it by digest for the same reason it pins everything else.
12.4 Consent is per plugin and per container
A host MUST obtain explicit consent from the person before loading a foreign plugin for the first time, and that request MUST name the plugin, name its format, and state that the plugin will run with the host's privileges.
Consent MUST be recorded against both the plugin's IRI and its container digest. A container whose digest has changed is a different body of code and MUST be consented to again.
A host MUST NOT treat consent for one foreign plugin as consent for another, MUST NOT offer a blanket setting that consents to all of them, and MUST NOT consent on the person's behalf.
A project that refers to a foreign plugin MUST NOT load it on open without consent. A stored project is data from wherever it came from, and treating it as authority to run code would make the consent meaningless.
12.5 Visibly marked
Wherever a foreign plugin appears, a host MUST mark it as foreign, and the mark MUST be available to assistive technology and MUST NOT be carried by colour alone.
A person who consented once and comes back a week later has no other way to tell the difference, and the difference is the whole of this section.
12.6 What still applies
Sections 4, 6, 7 and 10 apply unchanged: the real-time rules, events located by stream position, transport, and a failed plugin leaving the rest of the graph playing. A foreign plugin that breaks these is a defect in the adapter, not a permitted variation.
A host SHOULD generate the panel from the parameters the adapter reports rather than loading the foreign format's own interface. Once code is in the document that is not a security boundary, but a generated panel is consistent and accessible, which section 9.1 already says is the expected case rather than a degraded one.
13. Conformance
A host conforms if it implements sections 1 through 11 as written. Sections 12 and 14 are optional and a host that implements none of them conforms.
A plugin conforms if its profile validates against vocabs/shapes.ttl and its processor
obeys sections 3.3, 3.4, 4, 5.2, 6.2 and 8.
A plugin profile that validates but whose processor violates section 4.1 is not conformant and will not be reported as such by any automated check. That is a known gap, and it is why section 10.2 exists.
14. Composite plugins (optional)
A host MAY support plugins made of other plugins, defined in nested-plugins.md. A host that implements none of this section conforms.
A composite is a jig:CompositePlugin, which is not a jig:WebPlugin, declares no module or processor, and
is not mistaken for one that does. A host that does not support composites MUST say that the plugin is a
composite and cannot be run by it, rather than reporting a missing file.
A host that supports them MUST:
- fetch and validate the profile of the composite and of every member, recursively, before fetching any code, and evaluate
trn:requiresandjig:wasmFeatureover all of them (section 2.1); - check that every parameter an exposed port drives exists on its member and that the port's range lies within it, and refuse the composite otherwise;
- check each
jig:pinnedDigestagainst the canonical digest of the member's profile, and refuse on a mismatch; - refuse a composite that contains itself, directly or through its members, naming the chain, and support at least four levels of composite inside composite, naming its limit when it refuses for one;
- load the members as plugins, each by section 3, and load the composite whole or not at all, naming the composite, the member and the step on a failure (section 10.2);
- apply the author's settings and each exposed port's default when the composite loads;
- give the compiler the flattened graph, so that latency compensation and cycle detection (latency.md) act on every member;
- derive the composite's latency and tail, and never take them from its profile;
- key a composite's saved state by member IRI, ignore a key that names no member, and give a member with no key its defaults (section 8.1).
A host SHOULD list a composite's members and their origins before its first load, since a composite runs code from every author it names. Section 11 of nested-plugins.md lists each rule beside the test that guards it.