Plugin Manifest and Packaging

Define a plugin descriptor for schema versions 2, 3, and 4, and distribute the built JAR.

Every Turboism plugin JAR contains exactly one descriptor at:

META-INF/turboism/plugin.json

The descriptor declares "format": "turboism.plugin.meta" and a schemaVersion. Runtime accepts schema versions 2, 3, and 4, validates strictly against the matching contract, and does not upgrade a descriptor automatically.

VersionContract
2Base lifecycle, entrypoints, API range, dependencies, permissions, resources, and localization.
3Adds the required category field and optional tags (v3 is what the first-party plugins use).
4Adds declared cross-plugin event contracts: eventExports and eventImports.

Schema version 1 is not loaded. Unknown fields are rejected rather than ignored.

Manifest example

{
  "format": "turboism.plugin.meta",
  "schemaVersion": 3,
  "id": "dev.example.plugin.hello",
  "name": "Hello Plugin",
  "version": "0.1.0",
  "description": "Minimal Turboism plugin lifecycle example.",
  "category": "workflow",
  "tags": ["example", "lifecycle"],
  "entrypoints": [
    "dev.example.plugin.HelloPlugin"
  ],
  "turboismApi": "[0.1.0,0.2.0)",
  "authors": [
    { "name": "Example Author" }
  ],
  "license": "MIT",
  "website": "https://example.org/hello-plugin",
  "resources": [],
  "i18n": {
    "baseName": "META-INF/turboism/i18n/messages",
    "locales": []
  },
  "dependencies": [],
  "permissions": [],
  "capabilities": [],
  "environment": {
    "requiresCubism": false,
    "ui": "none"
  }
}

Required fields are id, name, version, entrypoints, turboismApi, authors, website, resources, and i18n; schema 3 additionally requires category.

Entrypoint rules

Each entrypoint must:

  • exist in the same plugin JAR;
  • be public;
  • implement TurboismPlugin or a subinterface;
  • expose a public no-argument constructor.

Multiple entrypoints share one plugin identity, descriptor, ClassLoader, PluginContext, DisposableScope, permission set, configuration namespace, storage namespace, resources, and localization declaration.

construct/init/enable: manifest order
disable/shutdown:       reverse manifest order

A failure during construction, initialization, or enablement rolls back the entire JAR.

Category and tags

category must be a lowercase kebab-case token of 2–32 characters. The reviewed first-party categories are modeling, workflow, appearance, analysis, performance, integration, system, and development. A well-formed category that is not registered still loads, is presented as other, and emits a structured PLUGIN_CATEGORY_UNKNOWN diagnostic.

tags is optional, holds at most 12 lowercase kebab-case tokens of 2–32 characters, and carries no permission or capability.

Dependencies

{
  "id": "dev.example.plugin.base",
  "version": "[1.0.0,2.0.0)",
  "type": "required",
  "ordering": "after",
  "reason": "Uses the base plugin contract."
}
  • type is required or optional.
  • ordering is none, before, or after.
  • dependency IDs use reverse-domain syntax.
  • version ranges use the Turboism version-range grammar.

A dependency record controls resolution and ordering. It does not make another plugin's private implementation package a public Java dependency.

Permissions

Each permission record contains a known ID, application or user scope, and a non-empty reason.

{
  "id": "turboism.action.register",
  "scope": "application",
  "reason": "Registers the Hello action."
}

Declare only permissions required by the SDK services the plugin calls. A permission does not guarantee Provider availability and does not bypass operation validation.

Resources and localization

Resource roots are normalized relative prefixes ending in /:

"resources": ["icons/"],
"i18n": {
  "baseName": "META-INF/turboism/i18n/messages",
  "locales": ["base", "en", "zh-Hans"]
}

The JAR must then contain:

icons/hello.png
META-INF/turboism/i18n/messages.properties
META-INF/turboism/i18n/messages_en.properties
META-INF/turboism/i18n/messages_zh_Hans.properties

Declared roots and catalogs must exist. Ordinary non-class resources must belong to a declared root. Path traversal, absolute paths, backslashes, empty segments, and undeclared resources are rejected.

Supported runtime locales are en, ja, ko, zh-Hans, and zh-Hant, plus the base bundle. Manifest locale IDs use the scripted form (zh-Hans); resource bundle file names use the Java underscore form (messages_zh_Hans.properties).

Cross-plugin event contracts (schema 4)

Schema 4 lets a plugin declare the public event types it owns and consumes, so Runtime can resolve them without ClassLoader delegation:

"eventExports": [
  {
    "id": "dev.example.event.hello",
    "contractVersion": "1.0.0",
    "eventType": "dev.example.api.HelloEvent",
    "abiSha256": "<sha256 of the event type bytes>"
  }
],
"eventImports": [
  {
    "provider": "dev.example.plugin.base",
    "eventId": "dev.example.event.base-status",
    "contractVersion": "1.0.0",
    "eventType": "dev.example.api.BaseStatusEvent",
    "abiSha256": "<sha256 of the event type bytes>"
  }
]

An eventImports entry may also carry "required": true to demand that its provider contract be admitted. Runtime validates the declared ABI digest for every entry, rejects duplicate export IDs or event types within a plugin, and refuses imports that do not resolve to an admitted provider contract.

Plugin IDs use reverse-domain syntax with at least one dot (dev.example.plugin.hello); event IDs use a lowercase dot/dash token form.

Build and validate

For an in-repository plugin module:

./gradlew :plugins:hello:test :plugins:hello:jar validatePluginMeta \
  --no-daemon --console=plain

The repository uses Java 17 and writes module build results under:

build/worktree/<worktreeId>/<module>/libs/

The JAR in libs/ is the release file. There is no wrapper archive and no separate packaging step.

Strict JAR validation

The JAR itself is the installation file. Runtime treats a selected JAR as untrusted local input and validates:

  • a regular file with no symbolic link in the source chain, under bounded raw size, per-entry size, total expansion, entry count, and compression-ratio limits;
  • exactly one META-INF/turboism/plugin.json;
  • descriptor ID, version, and API-range consistency;
  • no copied SDK, Runtime, test, test-framework, or Live2D classes;
  • no native binaries or installer payloads;
  • no nested JARs;
  • declared entrypoints, resource roots, and i18n catalogs present;
  • no undeclared ordinary resources.

Managed installation stages the validated JAR and applies it after Cubism restarts. Do not create a plugin by renaming an arbitrary ZIP or JAR. Always distribute the JAR produced by the Gradle build.