Skip to content

Substrate contract invariants

The substrate contract is the set of rules that govern what plugins may rely on from the substrate and what they MUST NOT touch. This page explains those rules and why they exist. For the inventory of surfaces plugins can rely on, see Substrate Contract.

Plugin PRs MUST touch only plugins/<plugin-name>/ and may import from approved substrate-facing packages (pkg/plugin/*, generated proto under pkg/proto/). That is the complete permitted footprint.

If your plugin needs a substrate capability that does not yet exist, that substrate change is separate work: its own issue, its own PR, its own review gate. Bundling a substrate change inside a plugin PR is forbidden — it bypasses the substrate review gates (crypto-reviewer, abac-reviewer, code-reviewer).

The dual invariant is INV-S2: substrate (internal/) MUST stay domain-free. No internal/ package may contain entity types, event vocabularies, or domain logic for scenes, channels, forums, discord, or any other specific use. INV-S1 keeps plugin work out of substrate; INV-S2 keeps domain knowledge out of substrate.

For the full boundary definition and anti-patterns, see ADR holomush-z1e7 — Strict Plugin-Boundary: Plugins Must Not Modify internal/.

INV-S5 requires set-equality between your manifest’s declared emit types and your code’s registered emit types. Mismatch fails plugin load with error code EVENT_TYPE_REGISTRY_MISMATCH. This catches two failure modes that the runtime emit gate misses:

  • Declared-but-unregistered: a crypto.emits entry the code never emits (dead declaration or typo).
  • Registered-but-undeclared: plugin code emitting a type the manifest never declared, silently as plaintext.

INV-S5 applies only to plugins with a non-empty crypto.emits block. Plugins without crypto.emits skip the check entirely.

To satisfy this invariant in your plugin, follow Register plugin emit types.

pkg/plugin/eventkit/ and pkg/plugin/groupkit/ are co-designed in the substrate-contract spec but their code lands only after N=2 validation (INV-S7): two distinct use plugins must adopt a primitive cleanly before it is extracted to substrate SDK code.

Today: plugins implement event-replay, group-membership, and focus-wire patterns inline (scenes-bespoke, channels-bespoke).

After N=2 validation: the primitive is extracted to the appropriate package (see the SDK table in Substrate Contract).

Forums uses eventkit only. Discord defaults to no SDK. INV-S10 forbids forums and discord from importing groupkit.

Relevant ADRs: