-
Notifications
You must be signed in to change notification settings - Fork 0
Capability Gating
Canonical reference: docs/functional-reference/capabilities.md. The authoritative list is the code: forge.api/Capabilities/CapabilityCatalog.cs in forge-api.
Capability gating is a per-install feature-flag substrate that lets one codebase ship identical binaries to a two-person trade shop, a 25-person job shop, an ISO-13485 medical-device manufacturer and a 500-person enterprise. Each install turns on a different set of features; the code is the same everywhere.
Every capability has a stable code in the form CAP-{AREA}-{NAME} — CAP-MD-CUSTOMERS, CAP-INV-LOTS, CAP-EXT-AI-ASSISTANT. Its enabled state lives in the capabilities table, and controllers, MediatR commands, HTTP routes and UI surfaces all read that state to decide whether a feature exists for this install.
Roughly 170 capabilities are registered at the time of writing; count the catalog rather than trusting a number in any doc, this one included.
The canonical example is the accounting boundary. An install can run with built-in lightweight invoicing and payments (CAP-ACCT-BUILTIN) or with QuickBooks/Xero/Sage as the source of truth (CAP-ACCT-EXTERNAL) — never both. Capabilities make that mutual exclusion declarative and enforced at a single gate layer, instead of scattering if (isStandalone) branches through the codebase. See Accounting Modes.
| Code | Area |
|---|---|
IDEN |
Identity, auth, users |
MD |
Master data — customers, parts, BOM, vendors, work centers, UOM |
P2P |
Procure-to-pay — POs, RFQs, receiving, bills, payments |
O2C |
Order-to-cash — quotes, sales orders, pick/pack/ship, invoicing, cash |
MFG |
Manufacturing — work order release, material issue, labour, completion, shop floor |
PLAN |
Planning — MRP, MPS, forecasting, capacity |
INV |
Inventory — core stock, lots, serials, cycle counts |
QC |
Quality — inspection, NCR, SPC, recall |
MAINT |
Maintenance — PM, breakdown, asset lifecycle |
ACCT |
Accounting — external provider, built-in books, full GL, expenses |
COSTING |
Standard costing and overhead |
HR |
Human resources — hiring, leave, payroll, training |
RPT |
Reports — operational, financial, OEE, dashboards |
CROSS |
Cross-cutting — notifications, documents, EDI |
EXT |
Extensions — kanban, chat, AI assistant |
MOBILE |
Mobile app surfaces |
PS |
Professional-services shapes |
Areas drive the grouping in the admin UI. Each catalog row carries its code, area, display name, description, a default-on flag, and an optional role gate for who may manage it.
Three layers, all reading the same snapshot:
-
CapabilityGateMiddleware— HTTP request level, keyed off[RequiresCapability]attributes on controllers and actions. -
CapabilityGateBehavior— a MediatR pipeline behaviour, so a command is refused even if it is reached from somewhere other than its controller. - The UI — surfaces are hidden rather than shown-then-denied.
A disabled capability yields a clean refusal rather than a 500 or a half-rendered screen. Concretely that is a 403 carrying an X-Capability-Disabled: CAP-… header and an error envelope whose code is capability-disabled — which is how a client tells "switched off for this install" apart from "you lack the role". API Access has the full error contract. CapabilityDependencyResolver enforces the relationships between capabilities, so enabling one that depends on another can't leave an install in an incoherent state, and CapabilitySnapshotProvider caches the resolved state per request.
Two rules worth knowing before you add a gate:
- Controllers marked with a class-level bootstrap attribute override action-level gates — the middleware checks bootstrap first. Mixed controllers must therefore be attributed action by action.
- Every controller is expected to carry a gate. An architecture test enforces this by reflection, with a shrink-only legacy register for exemptions.
Nobody wants to answer 170 yes/no questions. Forge ships nine named presets plus an explicit Custom opt-out, and a discovery wizard that recommends one from a short interview about how the shop actually works.
The recommendation engine is stateless — it maps answers to a preset. PRESET-CUSTOM is empty by design; applying it substitutes the catalog defaults.
Applying a preset does more than flip capability flags. The capability set is resolved and toggled through the same bulk substrate that gives you atomicity, validation, an audit trail and a live broadcast — and then each bundle the preset carries is applied in turn: terminology, roles and role templates, kanban track types and their stages, reference data, report visibility, dashboards, workflow definitions, and a cloud-storage folder map. Only some presets carry the reshaping bundles.
The part an admin should know before clicking is that the conflict policy differs per layer: terminology skips keys an admin has hand-edited, roles are add-only so re-applying never strips a granted permission, track types upsert by code, and the folder map is replaced wholesale. Re-applying a preset that already matches is a deliberate no-op with an audit row.
Modules (ModuleCatalog.cs) are the coarser, user-facing grouping the wizard speaks in — inventory, purchasing, sales and quoting, production, shipping, invoicing, quality, planning and scheduling, people.
Switching a capability on makes a surface exist. It does not make the thing behind it real: a stock install resolves most external integrations to mock implementations returning fabricated data. The install's readiness report is what tells you which enabled capabilities still have no credentials behind them. Check it before believing a feature works — see Configuration and Integrations.
A related mechanism worth knowing: TerminologyService and TerminologyPipe remap entity labels as admin configuration. Whether a shop calls the central object a Job, a Work Order or a Project is a settings row, not a code change.
Terminology is distinct from the other two ways words change in Forge: it renames the entity across the app, while per-install label overrides replace individual interface strings and translation carries them between languages. All three are covered on Customizing an Install.
Capabilities decide what this install has; roles decide what a person may do with it. They compose — a surface must be both enabled and permitted — and they are administered separately. See Access and Roles.
Forge · Apache 2.0 · built by Armory Works — this wiki maps the docs; the authoritative detail lives in docs/.
Evaluating
Running it
- Installation
- First Week
- Configuration and Integrations
- Hardening a Production Install
- Backup and Restore
- Upgrades and Rollback
- Accounting Modes
Using it
- App Surfaces
- Shop Floor Kiosk
- Mobile and Offline
- Access and Roles
- Customizing an Install
- Feature Reference
- UI Flows
Building on it
- API Access
- Architecture
- Capability Gating
- Workflow, Gates and Approvals
- Data Ownership and Export
- Glossary
Contributing
Repo wikis