JigDAW architecture
The specification is complete and normative, and a browser host implements it. This document says what the pieces are and where the boundaries fall, so that the normative host-plugin-contract.md has something to be normative about. Where a section below describes something not yet built, it says so; everything else describes what runs today.
The premise comes from first-thoughts.md, which is kept as written.
Layers
browser
┌──────────────────────────────────────────────────────────┐
│ ui/ webmcp/ │
│ the editor the agent surface │
│ \ / │
│ \ / │
│ ops/ one dispatcher, every operation once │
│ ========== | ======================================== │
│ rdf/ . model/ . compiler/ message thread │
│ ========== | ======================================== real-time boundary
│ engine/ │
│ AudioContext, one AudioWorkletNode per plugin │
│ each wrapping its own WebAssembly instance │
└──────────────────────────────────────────────────────────┘
│ │
plugin IRIs catalogue
anywhere on the web local today, a SPARQL store plannedTwo boundaries matter and they are not the same boundary.
The real-time boundary separates process() from everything else. Below it there is no
allocation, no network, no storage, no unbounded lock. Above it there is no timing
guarantee.
The operation boundary separates the control surfaces from the model. The editor and
the WebMCP face are both thin adapters over one dispatcher. Neither implements an operation
of its own. This comes from valis, where the GTK UI and the MCP server sit on one
OpDispatcher, and it is what stops the two drifting into disagreeing about what an edit
means.
Why one worklet per plugin
Each plugin is its own AudioWorkletProcessor, and the Web Audio API owns the graph, the
routing and the scheduling.
The alternative, a single worklet running a WebAssembly engine that hosts every plugin
internally, gives tighter control over processing order and latency and is closer to how a
native DAW works. It was not chosen because it makes dynamic loading hard, and dynamic
loading is the entire premise: a user finds a plugin, the host fetches it, and it is
running. Adding a node to a live AudioContext is ordinary; adding a plugin to a running
monolithic engine is not.
The cost is that Web Audio decides processing order, and that a graph with feedback needs explicit handling. That is a known trade and it is written down here so it is not rediscovered as a surprise.
Why a plugin is a URL
A plugin is identified by a dereferenceable https: IRI. Dereference it and you get its
profile. The profile says what the plugin is and links to the code. Identity, metadata and
delivery are one thing.
The reflex alternative is a registry, and it will be proposed again. A registry makes its operator the arbiter of what exists. Dereferenceable IRIs mean an author publishes by publishing, and a catalogue becomes one opinion about what is worth finding rather than the precondition for being found.
What this costs is spelled out in section 1.3 of the contract: CORS becomes a hard hosting requirement, and cross-origin isolation cannot be assumed. Those are not incidental. They are the price of the design and they shape the capability negotiation.
The catalogue
What runs today has no store. src/catalogue/LocalCatalogue.js indexes the plugins this
host serves by reading the same profile.ttl files it serves them from, in process, with no
database and nothing to crawl. It searches its own plugins first and, by default, only those:
a browser listing hundreds of plugins that none of them can run is a list rather than a browser.
The rest of the catalogue is one checkbox away, dimmed and labelled, because hiding it
entirely would misrepresent what exists. plugin-universe.com runs a public read-only SPARQL
endpoint at sparql.plugin-universe.com/public/query and a public MCP endpoint at
mcp.plugin-universe.com/mcp, over its whole catalogue with accepts and produces facets, under
CC0, and JigDAW's search reaches it through the same query service rather than through a
second implementation. Those figures were taken from /health and should be re-measured
rather than trusted.
Most of those plugins are native and not loadable here. That is fine and is the point of
jig:WebPlugin being a subclass: the catalogue knows about every plugin, and the host can
ask which of them it can actually run.
Not yet built: a local SPARQL store, crawling profiles into it, named graph per source.
The reasoning for the shape, when it is built, still holds: every triple in a graph saying
where it came from, with prov: metadata, never a shared catch-all graph, so that
re-crawling a source is a DROP and reload of that graph alone. This is plugin-universe's rule
and the reason is that a licence and a provenance are properties of a source, so one graph
per source means one answer per question. Neither a store nor crawling is needed for search
to work, which is why search shipped without them.
Discovered and curated
Two kinds of knowledge, inherited from transmission.
The profile is curated: what a plugin is for, what its signals mean, what it pairs with. An inspection is discovered: what a host observed when it actually loaded the module.
Discovery wins on technical facts. Curation wins on meaning. Neither is derivable from the other, which is why both exist.
Load failures are recorded rather than only reported. Which plugins work in which browsers is a fact about the ecosystem and is discoverable only if unsuccessful loads are written down.
WebMCP
The agent surface is a WebMCP server in the page, exposing the same operations as the editor through the same dispatcher.
Search is demand-driven, following transmission: an agent finds candidates by role and
signal semantics, then asks for a full description only for the ones that matter. Chain
validation checks that adjacent nodes agree, which is a type check on trn:produces against
trn:accepts, and reports the curated pairings and cautions.
The tool surface is specified in webmcp.md. The browser API it binds to is not settled, and that document says where the boundary between the two falls.
The vocabulary is a component too
JigDAW tells plugin authors to publish dereferenceable IRIs, so its own vocabulary
dereferences. http://purl.org/stuff/jigdaw/ already resolves, through an existing wildcard,
to https://hyperdata.it/xmlns/jigdaw/, and serving the vocabulary there is the whole job.
What it must serve, and why each term answers 303 rather than 200, is in
namespace.md.
IRIs are minted under the PURL and never under the host that serves them, so a change of hosting does not break IRIs already written into other people's project files.
Deployment
What runs today is one node process under systemd behind nginx, matching the catalogue
above having no store yet: there is nothing to put in one. Planned, when there is: a compose
stack adding a Fuseki store, with only nginx published and everything else bound to loopback.
Hosting is strandz.it, alongside hyperdata.it and plugin-universe.com, and the
arrangement follows plugin-universe's because copying one that is already running beats
inventing a better one that has never been operated.
The detail, including the headers a plugin origin must send and why an nginx add_header
inside a location is a trap, is in deployment.md.
Since settled
- The project file format is project-format.md. The
rdf:Listconflict between transmission's precedent and valis's rule went valis's way, and further: there is nordf:Listanywhere in a project. Processing order is derived from the connections because Web Audio derives it too, and everything else that looks ordered is really keyed. - Latency and feedback are latency.md. A cycle must contain an explicit delay of at least one render quantum, and latency inside a cycle is never compensated, because the delay in a feedback loop is the thing the user asked for.
What is not decided
- Whether a future local store, once built, caches plugin-universe's data or continues federating with live queries the way search already does without one.
- Clips, regions and an arrangement. The project format describes a patch, not a timeline.