# at.happyproto.plugin.release

> Published by [happyproto.at](https://lexicon.garden/identity/did:plc:mo7vq4jbioxwafy5zkcc3eux)

✓ This is the authoritative definition for this NSID.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:mo7vq4jbioxwafy5zkcc3eux/at.happyproto.plugin.release)
- [Documentation](https://lexicon.garden/lexicon/did:plc:mo7vq4jbioxwafy5zkcc3eux/at.happyproto.plugin.release/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:mo7vq4jbioxwafy5zkcc3eux/at.happyproto.plugin.release/examples)

## Definitions

### `at.happyproto.plugin.release#artifact`

**Type**: `object`

The installable WASM module. It must carry 'blob', 'url', or both; lexicons cannot express that, so publishing and indexing enforce it.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `blob` | `blob` | No | The module, stored as a blob in the publisher's PDS. |
| `checksum` | `string` | Yes | Lowercase base32 multibase-encoded multihash of the artifact bytes; sha2-256 (code 0x12) must be supported. For a blob it MUST equal the multihash inside blob.ref's CID. Where both sources are present, both must serve bytes matching it. A client rejects an unsupported hash function rather than skipping verification. |
| `contentType` | `string` | No |  |
| `url` | `string` (uri) | No |  |

### `at.happyproto.plugin.release#artifacts`

**Type**: `object`

Map of artifact type to artifact. 'package' is the installable module and is required; 'manifest' is its manifest.json, which is what says how to load a module published as a blob, since such a module has no directory beside it to hold a sibling file; the rest are presentation assets a browser renders from the index without fetching anything. A custom type takes an 'x-' prefix, so a type added here later cannot collide with someone's extension; such keys travel as extra properties and are validated out of band, as 'requires' is.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `banner` | `ref` → `#imageArtifact` | No |  |
| `icon` | `ref` → `#imageArtifact` | No |  |
| `manifest` | `ref` → `#manifestArtifact` | No |  |
| `package` | `ref` → `#artifact` | Yes |  |
| `screenshots` | `array` | No |  |

### `at.happyproto.plugin.release#imageArtifact`

**Type**: `object`

A presentation asset. SVG is deliberately absent: it carries script and renders in an operator's dashboard.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `alt` | `string` | No |  |
| `blob` | `blob` | No |  |
| `checksum` | `string` | Yes |  |
| `contentType` | `string` | No |  |
| `height` | `integer` | No |  |
| `url` | `string` (uri) | No |  |
| `width` | `integer` | No |  |

### `at.happyproto.plugin.release`

**Type**: `record`

One version of a package. The record key is '<slug>:<version>'. A release at a given version is never modified: a registry that later sees a different CID at this key keeps the first and flags the conflict.

**Key**: `any`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `artifacts` | `ref` → `#artifacts` | Yes |  |
| `extensions` | `unknown` | Yes | Keyed by NSID. MUST carry 'at.happyproto.plugin.releaseExtension', which is the manifest. |
| `notes` | `string` | No | What changed in this release, as Markdown. A client renders this to tell an operator what an upgrade brings, so it is publisher-supplied text shown to someone else: a renderer MUST NOT render embedded HTML, and MUST refuse a link scheme it does not recognise. Long enough for a changelog and no longer; `repo` is where the full history lives. |
| `package` | `string` | Yes | The profile's slug. MUST equal the pre-':' portion of the record key. |
| `repo` | `string` (uri) | No | Source repository for this release. |
| `requires` | `unknown` | No | Dependencies, as a map lexicons cannot type. Every key is 'env:happyview' or an 'at://<did>/at.happyproto.plugin.profile/<slug>' URI; every value is a semver range. Keyed by AT URI rather than by DID because identity here is (publisher, slug) and one publisher may hold many packages. Validated by the registry and the installer. An unrecognised 'env:' key counts as unfulfilled, which warns rather than refuses. |
| `version` | `string` | Yes | Semver 2.0 without build metadata, since '+' is not a legal record-key character. MUST equal the post-':' portion of the record key. Dropping build metadata also means it can never affect version precedence, which semver says it should not. |

### `at.happyproto.plugin.release#manifestArtifact`

**Type**: `object`

The module's manifest.json. A module published as a blob has no directory beside it to hold a sibling file, so this is the only thing that says how to load it. It must carry 'blob', 'url', or both; lexicons cannot express that, so publishing and indexing enforce it.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `blob` | `blob` | No | The manifest, stored as a blob in the publisher's PDS. |
| `checksum` | `string` | Yes | As '#artifact.checksum': the multihash of the bytes, which whichever source serves them is verified against. |
| `contentType` | `string` | No |  |
| `url` | `string` (uri) | No |  |

## Raw Schema

```json
{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "artifact": {
      "description": "The installable WASM module. It must carry 'blob', 'url', or both; lexicons cannot express that, so publishing and indexing enforce it.",
      "properties": {
        "blob": {
          "accept": [
            "application/wasm"
          ],
          "description": "The module, stored as a blob in the publisher's PDS.",
          "maxSize": 16777216,
          "type": "blob"
        },
        "checksum": {
          "description": "Lowercase base32 multibase-encoded multihash of the artifact bytes; sha2-256 (code 0x12) must be supported. For a blob it MUST equal the multihash inside blob.ref's CID. Where both sources are present, both must serve bytes matching it. A client rejects an unsupported hash function rather than skipping verification.",
          "maxLength": 256,
          "type": "string"
        },
        "contentType": {
          "maxLength": 128,
          "type": "string"
        },
        "url": {
          "format": "uri",
          "type": "string"
        }
      },
      "required": [
        "checksum"
      ],
      "type": "object"
    },
    "artifacts": {
      "description": "Map of artifact type to artifact. 'package' is the installable module and is required; 'manifest' is its manifest.json, which is what says how to load a module published as a blob, since such a module has no directory beside it to hold a sibling file; the rest are presentation assets a browser renders from the index without fetching anything. A custom type takes an 'x-' prefix, so a type added here later cannot collide with someone's extension; such keys travel as extra properties and are validated out of band, as 'requires' is.",
      "properties": {
        "banner": {
          "ref": "#imageArtifact",
          "type": "ref"
        },
        "icon": {
          "ref": "#imageArtifact",
          "type": "ref"
        },
        "manifest": {
          "ref": "#manifestArtifact",
          "type": "ref"
        },
        "package": {
          "ref": "#artifact",
          "type": "ref"
        },
        "screenshots": {
          "items": {
            "ref": "#imageArtifact",
            "type": "ref"
          },
          "maxLength": 8,
          "type": "array"
        }
      },
      "required": [
        "package"
      ],
      "type": "object"
    },
    "imageArtifact": {
      "description": "A presentation asset. SVG is deliberately absent: it carries script and renders in an operator's dashboard.",
      "properties": {
        "alt": {
          "maxGraphemes": 300,
          "maxLength": 3000,
          "type": "string"
        },
        "blob": {
          "accept": [
            "image/png",
            "image/jpeg",
            "image/webp",
            "image/gif",
            "image/avif"
          ],
          "maxSize": 1048576,
          "type": "blob"
        },
        "checksum": {
          "maxLength": 256,
          "type": "string"
        },
        "contentType": {
          "maxLength": 128,
          "type": "string"
        },
        "height": {
          "minimum": 1,
          "type": "integer"
        },
        "url": {
          "format": "uri",
          "type": "string"
        },
        "width": {
          "minimum": 1,
          "type": "integer"
        }
      },
      "required": [
        "checksum"
      ],
      "type": "object"
    },
    "main": {
      "description": "One version of a package. The record key is '<slug>:<version>'. A release at a given version is never modified: a registry that later sees a different CID at this key keeps the first and flags the conflict.",
      "key": "any",
      "record": {
        "properties": {
          "artifacts": {
            "ref": "#artifacts",
            "type": "ref"
          },
          "extensions": {
            "description": "Keyed by NSID. MUST carry 'at.happyproto.plugin.releaseExtension', which is the manifest.",
            "type": "unknown"
          },
          "notes": {
            "description": "What changed in this release, as Markdown. A client renders this to tell an operator what an upgrade brings, so it is publisher-supplied text shown to someone else: a renderer MUST NOT render embedded HTML, and MUST refuse a link scheme it does not recognise. Long enough for a changelog and no longer; `repo` is where the full history lives.",
            "maxGraphemes": 3000,
            "maxLength": 30000,
            "type": "string"
          },
          "package": {
            "description": "The profile's slug. MUST equal the pre-':' portion of the record key.",
            "maxLength": 512,
            "type": "string"
          },
          "repo": {
            "description": "Source repository for this release.",
            "format": "uri",
            "type": "string"
          },
          "requires": {
            "description": "Dependencies, as a map lexicons cannot type. Every key is 'env:happyview' or an 'at://<did>/at.happyproto.plugin.profile/<slug>' URI; every value is a semver range. Keyed by AT URI rather than by DID because identity here is (publisher, slug) and one publisher may hold many packages. Validated by the registry and the installer. An unrecognised 'env:' key counts as unfulfilled, which warns rather than refuses.",
            "type": "unknown"
          },
          "version": {
            "description": "Semver 2.0 without build metadata, since '+' is not a legal record-key character. MUST equal the post-':' portion of the record key. Dropping build metadata also means it can never affect version precedence, which semver says it should not.",
            "maxLength": 256,
            "type": "string"
          }
        },
        "required": [
          "package",
          "version",
          "artifacts",
          "extensions"
        ],
        "type": "object"
      },
      "type": "record"
    },
    "manifestArtifact": {
      "description": "The module's manifest.json. A module published as a blob has no directory beside it to hold a sibling file, so this is the only thing that says how to load it. It must carry 'blob', 'url', or both; lexicons cannot express that, so publishing and indexing enforce it.",
      "properties": {
        "blob": {
          "accept": [
            "application/json"
          ],
          "description": "The manifest, stored as a blob in the publisher's PDS.",
          "maxSize": 1048576,
          "type": "blob"
        },
        "checksum": {
          "description": "As '#artifact.checksum': the multihash of the bytes, which whichever source serves them is verified against.",
          "maxLength": 256,
          "type": "string"
        },
        "contentType": {
          "maxLength": 128,
          "type": "string"
        },
        "url": {
          "format": "uri",
          "type": "string"
        }
      },
      "required": [
        "checksum"
      ],
      "type": "object"
    }
  },
  "id": "at.happyproto.plugin.release",
  "lexicon": 1
}
```
