COLONY — THE ASSISTANTS · v0.4.2.14
The module contract
Source: modules/anthill/docs/MODULE_CONTRACT.md, a path from the root of the product's repository — versioned with the code, rendered here at every release.
v0.3.20.0 — W3-10. How a first-party module enters the colony: one declaration, one registry, one
set of fixtures. ADR-007 drew the boundary (a module references the SDK and nothing else of ours;
the core never names a module type); this is what a module says about itself across that boundary.
One manifest, nine sections
Anthill.SDK.Modules.ModuleManifest. Every first-party module — tools, reasoning,
infrastructure, knowledge, micromound — carries one as public static readonly ModuleManifest Contract and implements IManifestedModule, which adds exactly one member to IAnthillModule:
Manifest. The interface stays small on purpose: a manifest is a declaration a module OFFERS, never
a hook the host calls into.
| Section | Fields | What it settles |
|---|---|---|
| identity and compatibility | id, version, platform_api, contracts, dependencies |
the id is the registry's key and equals Name; platform_api is the SDK contract the module was built against; contracts names the wire and store versions it speaks (micromound.protocol, forager.engine); a dependency is required or optional, with a minimum version |
| lifecycle | runtime, lifecycle |
in_host, supervised_service (the host starts, verifies, restarts and stops it — FORAGER) or remote_device (mounds dial in; the host never reaches out); the operations it supports, from register · install · configure · start · health · drain · stop · upgrade · remove — a supervised service must declare start, health and stop |
| user interface | ui.door, ui.route_prefixes, ui.settings_pane |
the area of the rail it owns (Knowledge, Devices), the API routes it maps, the Settings pane that configures it |
| permissions | operation, role, service_scope, approval |
what each operation needs: a W1-02 project role (viewer · operator · project_admin · owner, or installation for the deployment's own surfaces), the scope a service identity needs, whether it goes through the approval lane |
| commercial | product_id, capabilities, limit_keys, quantity_keys, on_expiry |
the catalog product, the gated capabilities it grants — never one the catalog marks never_gated — its limit and quantity keys, and what a lapsed subscription leaves (read_only, no_new_allocations); null for a module of the base product |
| jobs and events | jobs.kinds, jobs.event_prefixes |
the durable job kinds it runs and the event types it publishes |
| data ownership | data.tables, data.stores, data.export_formats, data.retention |
the tables it owns in the colony's store — every one classified in TenancyTables — the stores it owns outside it, what it exports, who decides retention |
| operations | diagnostics_route, log_prefix, rollback |
where the technical detail lives (never the main flow), its log prefix, what an upgrade can undo |
| qualification | qualification |
the cases in qualification/cases.json that exercise it |
It fails at build
ModuleManifestValidator is the one rule set — pure, in the SDK, so a module's own tests can run
it. ModuleContractTests runs it over every module the build compiles, and then checks what the
validator cannot: the manifest agrees with the module's Name and Version; the JSON fixture under
packages/contracts/module-manifest/fixtures/valid/<id>.json is structurally equal to the code;
every table it owns is one TenancyTables classifies, and the module lists are exactly equal;
every route prefix and the diagnostics route name routes Anthill.Api maps; a door is a domain of
the console's IA with the same label and route; the commercial vocabulary is the catalog's; every
qualification case exists. packages/contracts/module-manifest/manifest.validate.zw.py proves the
same fixture set from outside the .NET build — schema, catalog, case list, module tree — and CI
runs it as the contracts job.
It refuses at load, and the colony keeps running
ModuleRegistry.Resolve takes the set the composition root hands ModuleHost.LoadAll and returns
a plan: which modules load, in an order that puts a dependency before what needs it, and which
are refused with a reason a person can act on. A module built against a newer platform API than
this host offers is refused. A module whose required dependency the build does not carry is
refused; a dependant of a refused module is refused too, and says so. An optional dependency that
is missing degrades the module — it loads, the registry notes what it lacks, and what needs it
refuses at call time. Nothing in this path throws: the base application stays usable and the
failure is explained (A18). The two build defects still throw, as a throwing Register always
has: a manifest that does not validate, and two modules claiming one id.
GET /modules carries the registry beside the five-axis door states: platform_api,
manifest_schema_version, every loaded manifest in full, modules loaded unmanifested (a
third party's zero-core-edit module still loads without one), degraded and refused. Settings →
Modules → Registry shows it: one row per manifest, and a refusal in red with its reason.
What this is not
First-party modules only. A public marketplace, untrusted plugin loading, revenue sharing and runtime downloads are later initiatives; nothing here builds a sandbox, and nothing forecloses one — a manifest that arrived as JSON from a package would validate against the same schema, and the registry does not care where a module came from, only what it declares and whether the build can honour it.
