# Plugin Contract ## Manifest `manifest.json` is the runtime contract consumed by Tribes: - `id` is the globally unique plugin identity; `slug` is the local URL/assets namespace. - `otp_app` should be vendor-prefixed and must match the Mix application name. - `entry_module` must be a loadable module ending in `.Plugin`. - `provides` declares capabilities exported by the plugin. - `requires` declares hard plugin/API contracts beyond the `host_api` foundation. - `enhances_with` declares optional host capabilities. - `migrations` should be `true` when `priv/repo/migrations` contains plugin migrations. - `children` should be `true` when the plugin starts its own supervision tree. `host_api` is the versioned foundation contract. It provides the supported Phoenix, Ash, PubSub, data, and cluster-event APIs for plugins. Do not add separate framework capability requirements for APIs that belong to that foundation. Declare `org.tribe-one.caps.ui@1` in `requires` when rendering through `Tribes.Plugin.Layouts.app`, importing `Tribes.UI.Components`, or using `use Tribes.UI`. ## Entry Modules The plugin entry module is: ```elixir defmodule TribeOne.TribesPlugin.Kobold.Plugin do use Tribes.Plugin.Base, otp_app: :tribe_one_kobold end ``` Keep plugin implementation code under your own namespace. The default generator uses `TribeOne.TribesPlugin.*`; third-party plugins should use their own organization or project namespace. ## Host Dependencies `tribes_plugin_api` is the release-facing `host_api` foundation. It carries the supported plugin behaviours, helpers, Phoenix/Ash data surface, and sync DSL. Do not add the full `:tribes` app as a production dependency. Host services are exposed through public facade modules in `Tribes.Plugin.Services`. Use `Tribes.Plugin.Services.Alliance` for local tribe metadata and tribe users, `Tribes.Plugin.Services.Metrics` for compact metric rollups, and `Tribes.Plugin.Services.Logs` for operational plugin logging. ## Localization Keep Kobold-specific copy in Kobold's own Gettext backend and `priv/gettext` catalogs. For shared Tribes UI labels that are documented as public plugin API, import `Tribes.Plugin.Gettext` and call `tgettext/2`: ```elixir import Tribes.Plugin.Gettext tgettext("Cancel") tgettext("Save") ``` Do not call arbitrary internal Tribes Gettext domains from plugin code. The generated plugin intentionally splits the `:tribes` path dependency by environment: - In `:dev`, `:tribes` is compile-only. This lets `mix compile` create the entry-module beam expected by the host plugin manager without starting a nested Tribes application. - In `:test`, `:tribes` is runtime-enabled. Host-backed tests need the real repository, endpoint pipeline, and plugin contract helpers. ## Ash Resources For cluster-synced plugin data, add Ash resources under the plugin namespace and use `extensions: [AshNostrSync]` only for data that should replicate across the cluster. Prefer one UUID primary key per synced resource. Document any local-only tables, retention policies, or side effects in plugin docs. ## Kobold Dataset Resources Kobold's MVP uses fixed plugin-owned Ash resources as its storage and sync contract. User-defined dataset resources are dynamic schema data, not runtime Elixir modules, runtime Ash resources, or per-resource database tables. Initial fixed resources: - `Dataset` - `ResourceDefinition` - `DatasetEvent` - `RecordProjection` - `MergeProposal` The first shipped foundation includes `Dataset`, `ResourceDefinition`, `DatasetEvent`, and local-only `RecordProjection`. Public dataset/resource/event mutations use `AshNostrSync.PublishChange`; private mutations deliberately do not publish and remain local. `RecordProjection` is rebuildable from `DatasetEvent` history. `MergeProposal` remains planned for the fork/merge milestone. Do not generate Ash resources or migrations for each user-defined dataset resource in the first cut. If a future optimized dataset type needs dedicated Ash resources, document that as a specialized plugin compatibility layer rather than the generic Kobold storage model. ## Local Supertest API Kobold exposes a loopback-only JSON API under `/plugins-api/tribe-one-kobold` for smoke and supertest scenarios: - `GET /health` - `GET /schema` - `POST /reset` - `POST /datasets` - `POST /datasets/:dataset_id/resources` - `POST /datasets/:dataset_id/records` - `GET /state?run_id=...` - `POST /projections/rebuild` This API is not the durable user-facing federation contract; it exists to prove plugin install, migrations, public dataset sync, and private local-only dataset behavior in live scenarios.