# JSON Schemas

> The JSON Schemas Maru publishes at maru.audio/schema, the alpha rule they follow, how to point maru.config.json at one with $schema, and what a schema does not check.

Canonical: https://maru.audio/docs/schemas
Documentation index: https://maru.audio/llms.txt

Maru publishes a JSON Schema for each file and message format whose shape is
defined in its contracts. Point a file at one with `$schema` and your editor
or coding agent completes keys and flags shape errors while you type. The
schemas are generated from the same definitions `maru module check` uses, so
the editor and the check report the same shape errors.

## What is published

The index at [`https://maru.audio/schema`](https://maru.audio/schema) lists
every schema as JSON, grouped by noun, with its URL, stability and the page
that explains it. Each schema lives at
`https://maru.audio/schema/<noun>/<document>/alpha.json`. The noun says what
the document belongs to:

- **project**: authored source, the files you write in a Project.
- **release**: the published bundle a Project builds into.
- **face**: the protocol between a module's interface (its face) and its host.
- **preview**: the protocol between an embedded preview frame and its page.

| Noun | Name | URL | Describes | Written by |
| --- | --- | --- | --- | --- |
| project | `project/config` | [`/schema/project/config/alpha.json`](https://maru.audio/schema/project/config/alpha.json) | `maru.config.json`, the Project config | You |
| release | `release/web-package` | [`/schema/release/web-package/alpha.json`](https://maru.audio/schema/release/web-package/alpha.json) | `web-package.json`, the web package manifest inside a Release | The toolchain |
| face | `face/message` | [`/schema/face/message/alpha.json`](https://maru.audio/schema/face/message/alpha.json) | A message from a face to its host | The runtime |
| face | `face/host-message` | [`/schema/face/host-message/alpha.json`](https://maru.audio/schema/face/host-message/alpha.json) | A message from the host to a face | The runtime |
| face | `face/host-diagnostic` | [`/schema/face/host-diagnostic/alpha.json`](https://maru.audio/schema/face/host-diagnostic/alpha.json) | A diagnostic message from the host to a face | The runtime |
| preview | `preview/frame-message` | [`/schema/preview/frame-message/alpha.json`](https://maru.audio/schema/preview/frame-message/alpha.json) | A message from a preview frame to its host | The runtime |
| preview | `preview/host-message` | [`/schema/preview/host-message/alpha.json`](https://maru.audio/schema/preview/host-message/alpha.json) | A transport or parameter message from the host to a preview frame | The runtime |
| preview | `preview/parent-message` | [`/schema/preview/parent-message/alpha.json`](https://maru.audio/schema/preview/parent-message/alpha.json) | A message from the parent page to an embedded preview (`?host=parent`) | The runtime |
| preview | `preview/param` | [`/schema/preview/param/alpha.json`](https://maru.audio/schema/preview/param/alpha.json) | The parameter list a preview frame reports | The runtime |

Only `project/config` is for a file you write. The others are published so
other implementations and tools can check what Maru produces.

`release.json`, the manifest of a Release, has no published schema yet. The
toolchain writes it and validates it when it reads it; you never edit it.

## The alpha rule

Every URL is `alpha`. The contracts are still changing and have not been
confirmed, so nothing is frozen yet:

- An `alpha` URL always serves the schema the current Maru contracts
  generate. It can change with any website release: a key can be added, and
  a value that used to pass can be refused.
- A frozen version (for example `v1.json`) will never change once published.
  None exists today. When one is published, the index lists it and `alpha`
  keeps tracking the newest shape.

Responses are cached for five minutes and may be fetched from any origin.

A removed published property or schema URL is a breaking contract change,
including during `alpha`. Narrowing a type or allowed value is also breaking.
Repository maintainers can compare the registered schema shapes against an
earlier commit with:

```sh
node packages/audio-contracts/bin/diff-schemas.mjs <git-ref> --json
```

The comparison reads historical and current source, reports publication names
and changed paths, and refuses breaking or uncertain changes. It also refuses
any changed or deleted frozen `vN.json` snapshot. There is no breaking-change
override yet, and this comparison is not wired into the automatic schema
freshness check. It does not test handwritten validation rules or prove that
an existing Release still runs; those need their owning tests and runtime
qualification.

## The Project config

A Project is configured by `maru.config.json` at its root. `maru init` writes
one for new Projects:

```json
{
  "$schema": "https://maru.audio/schema/project/config/alpha.json",
  "type": "audio-module",
  "entry": "./src/index.ts",
  "development": {
    "sources": {
      "module": "src/index.ts",
      "dsp": "src/dsp/processor.ts",
      "react": "src/ui/index.tsx"
    }
  },
  "listing": { "license": "maru-remix-1.0" }
}
```

The `$schema` line is the only thing an editor needs. VS Code and other
editors with JSON Schema support read it with no extension or setting.
`maru module check` refuses a `$schema` that names any other
`maru.audio/schema` URL, so a Project never points at the wrong document.

The same object can live under the `maru` key of `package.json`, which is
where older Projects keep it. Editors check `package.json` against the npm
schema, which ignores that key, so you get no completion there. For now a new
Project keeps the object in both places, because some Maru tools still read
`package.json`. The two must stay equal: when both are present and differ,
`maru.config.json` wins and `maru module check` reports the difference.
`maru module license on|off` updates both.

Keys this version of the schema does not name are accepted, so a Project
written for a newer Maru still opens with an older one. Named keys are
checked strictly. For example, `share.dsp` must be `source`, `program` or
`product`, and `share` accepts only `dsp` and `face`.

## Generated documents and messages

The other schemas describe documents and messages Maru tools produce. They
carry `x-maru-contract`, the internal contract they come from, and
`x-maru-stability: "alpha"`. Nested values whose rules belong to another part
of Maru are marked `x-maru-delegate` or `x-maru-opaque` and are only loosely
described. Binary fields are marked `x-maru-binary`; they travel between
windows and workers and never appear in JSON.

## What a schema does not check

A schema checks shape: which keys exist, their types and their allowed
values. Some rules cannot be stated that way, and Maru checks them in code:

- **Refinements.** Rules across fields, such as "not both `workInProgress`
  and `localOnly`" or "exactly one of `sku` and `projectId`", are listed by
  name under `x-maru-refinements` but are not enforced by the schema.
- **Files on disk.** Whether the entry, the DSP source or a bank manifest a
  path names exists, and whether a `source` DSP share has the matching
  `exports` entry in `package.json`.
- **Release identity.** Bundle digests, per-file digests and
  `releaseSha256`: a `.maru` bundle is checked when it is opened, not by a
  schema.
- **What a Release requires.** Whether a host meets a Release's `requires`
  (engine, runtime line, interface and capabilities).
- **Your module itself.** Parameters, ports, presets and the face contract
  are checked by the declaration and face gates.

[`maru module check`](https://maru.audio/docs/export.md#check-the-module) runs all of these. When it and an editor
disagree, the check is right.
