/api/v1 — One Capability-Gated Contract¶
Status: governance. Rewritten 2026-06-16 (capability model). Supersedes the
prior "two divergent lineages" split (2026-06-15): there is one /api/v1
contract; implementations differ by the capabilities they advertise, not by a
separate spec.
Why one contract (not two)¶
The split model declared a frozen MFC spec and a forward rust spec with no interchangeability. But we want the opposite: a single controller (TrackMuleBB) that drives both eMuleBB MFC and emulebb-rust, with guaranteed alignment, while emulebb-rust adds indexing endpoints the MFC will never implement. A divergent-spec model cannot give that. A capability-gated single contract can: the MFC is simply a subset that advertises fewer capabilities.
The model¶
- One canonical OpenAPI document. emulebb-rust is the superset / source of
truth; the MFC implements a subset of the same spec. The indexing endpoints
live in this one spec, tagged as the
indexing.*capability — the MFC just does not advertise them. - Per-operation capability tags. Each operation carries
x-capability: <group.feature>andx-since: <contract-version>extensions, so the spec itself records which capability (and since which version) each operation belongs to. Examples:core.search,core.transfers,core.shared,core.kad,indexing.snoop,indexing.torznab,arr.qbit-compat. - Runtime discovery. Every implementation answers:
- eMuleBB MFC returns
core.*only (its frozen subset). - emulebb-rust returns
core.* + indexing.* + arr.*(and grows additively). - The soft-frozen MFC includes
/capabilitiestoo (decision 2026-06-16). The capabilities endpoint is a contract-discovery primitive, not a product capability, so it is allowed as contract maintenance under the MFC soft freeze. This is precisely what guarantees clean evolution: every core — frozen or forward — is discoverable the same way, so the controller never special-cases a product and new cores/capabilities slot in without touching it. (Tracked for the MFC asFEAT-122.) - Capability negotiation in the controller. TrackMuleBB reads
/capabilitieson connect, builds a feature set, and only calls operations whose capability is advertised. The UI hides unsupported features (e.g. no indexer/Torznab tab for an MFC core). Noif (client == "mfc")branching — behaviour is capability-driven.
Versioning¶
- The contract is versioned as a contract semver (
contractVersion/x-contract-version), independent of any product release tag. - Additive endpoints/fields (e.g. new indexing operations) bump the minor; breaking changes bump the major with a documented migration.
- The MFC, frozen, advertises whatever it shipped (a stable subset at a pinned
contractVersion); it never grows. emulebb-rust leads the version forward. - Controllers target a contract-version range + capabilities, never a product tag, and degrade by capability.
Alignment guarantee = conformance tests (not the spec alone)¶
The spec declares the contract; CI conformance proves alignment:
- emulebb-rust validates its live
/api/v1responses against the full canonical OpenAPI (drift check in rust CI). - eMuleBB MFC validates its live responses against the subset it advertises
(only the operations for its
/capabilities). - Subset invariant:
capabilities(MFC) ⊆ capabilities(rust) ⊆ capabilities(spec). - Capabilities-endpoint contract: every implementation must answer
/capabilities, and every advertised capability must map to operations present and conformant.
Tooling: a property-based/contract validator against the OpenAPI (e.g.
Schemathesis, Dredd, or openapi-core), run per advertised capability. This
replaces the hand-maintained compatibility matrix.
Implementation matrix (informational; the tests are authoritative)¶
| Capability group | eMuleBB MFC | emulebb-rust |
|---|---|---|
core.* (search, transfers, shared, kad) |
✅ (frozen subset) | ✅ |
indexing.* (autonomous Kad/eD2K index, Torznab) |
— (never) | ✅ (additive) |
arr.qbit-compat (qBittorrent-compatible download client) |
adapter (frozen) | ✅ |
| Contract version | pinned (frozen) | leads (semver) |
Legend: ✅ advertised + conformant · — not advertised (controller hides it).
Consequences¶
- One controller suffices. Because the MFC is just a low-capability
/api/v1client, TrackMuleBB drives it for free via capability negotiation — so aMuTorrent is deprecated as a separate controller (see SUITE-JOINT-ROADMAP / PRODUCT-PORTFOLIO). - emulebb-rust can add indexing (and future) capabilities additively without any obligation to the MFC and without breaking the controller.
- MFC support is free fallout of the generic path, not a feature investment — the MFC stays frozen.
Next steps (tracked, not done here)¶
- [ ] Add
x-capability+x-sinceto the canonical OpenAPI and aGET /api/v1/capabilitiesoperation (emulebb-rust owns the spec). - [ ] emulebb-rust CI: capability-aware conformance/drift check (extend the existing REST contract item).
- [ ] eMuleBB MFC: add
GET /api/v1/capabilities(advertising its frozencore.*subset) under the soft freeze as contract maintenance, plus a conformance run against that subset. Tracked asFEAT-122. - [ ] TrackMuleBB: capability negotiation + UI gating (see
TMBB-FEAT-*).
Related: SUITE-JOINT-ROADMAP,
PRODUCT-PORTFOLIO,
REST-API-CONTRACT,
emulebb-rust/docs/rest/REST-API-OPENAPI.yaml (canonical superset).