About fitness-functions
EventCatalog Fitness Functions is a skill for defining architecture rules as TypeScript checks over an EventCatalog catalog. Teams can report violations and use the CLI's non-zero exit status to gate pull requests; checks cover documented catalog data, not running systems.
Key features
- 01TypeScript architecture rules
- 02Read-only catalog SDK queries
- 03Violation reports with resource links
- 04Error and warning severities
- 05CLI checks for pull-request gates
Use cases
- 01Enforcing domain ownership boundaries
- 02Checking dependencies across catalog resources
- 03Flagging missing owners or message channels
- 04Gating catalog changes in CI
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ event-catalog/ skills --skill fitness-functionsAfter installing, type/fitness-functionsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api.mdci.mdexamples.mdDetails
From the project READMEEventCatalog Fitness Functions
Fitness functions are architecture rules written as code. Each one is a TypeScript file that reads the catalog through a read-only SDK and returns the violations it finds. eventcatalog fitness check runs them all, prints the results, writes a JSON report and exits non-zero when a rule fails, so the rules can gate pull requests.
The team decides what a healthy architecture looks like; EventCatalog runs the rules and reports the results.
Fitness functions check the architecture the catalog documents, not the running systems. A rule is only as good as the catalog data it reads.
Requirements (check these FIRST)
- Version —
@eventcatalog/core5.4.0 or later (check the catalog'spackage.json). Alternatively@eventcatalog/cli1.4.0 or later runs the same check without core (npx @eventcatalog/cli fitness check). Node.js 20 or later. - Editor types (optional) — fitness functions run without installing anything else, but for types and autocomplete install
@eventcatalog/fitnessas a dev dependency. Catalogs created withcreate-eventcatalogalready have it, plus two starter functions infitness/and annpm run fitnessscript.
Fitness function, linter or governance?
Pick the right tool before writing code:
| The rule is about… | Use |
|---|---|
| A single file being valid: frontmatter fields, required properties, broken references, file layout | @eventcatalog/linter |
| What changed in a pull request (a new consumer, a schema change, a deprecation), with notifications | eventcatalog governance check (governance.yaml) |
| The shape of the architecture: ownership, naming, boundaries, dependencies, who can access what, across many resources | A fitness function (this skill) |
Instructions
Step 1: Locate the catalog and what already exists
Find the catalog (the folder with eventcatalog.config.js) and check:
- Is there a root
fitness/folder, orfitness/folders inside resource folders? Read the existing functions — reuse their helpers and match their style, and don't create a rule that duplicates one. - Fitness function ids must be unique across the catalog. List the ids already in use.
- Does
package.jsonhave afitnessscript? Is@eventcatalog/fitnessinstalled? - Is
.eventcatalog/fitness/in.gitignore? The check writes its report there; it shouldn't be committed.
Step 2: Turn the rule into a precise check
A rule like "services should own their data" is too vague to code. Pin down, asking the user when it isn't clear:
- What exactly is a violation? ("A service writes to a data store that belongs to another domain's systems.")
- Which resources does it apply to? All of them, one domain, one system, one service?
- Latest versions only, or every version? Almost always latest (
{ latestOnly: true }). - Severity —
errorfails the check (and CI);warningis reported but doesn't fail it. Start new rules on an existing catalog aswarningif they'll find many violations, and tighten later. - What should each violation point at, and how is it fixed? Every violation should name the resources involved and suggest the fix.
Check the catalog data supports the rule: if the rule needs writesTo, owners or sends.to and the catalog doesn't document them, say so — the rule would pass vacuously.
Step 3: Choose where the file goes
Where the file lives decides the resource it receives — not what it can see: every function can query the whole catalog.
| Rule applies to | Put it in | resource is |
|---|---|---|
| The whole catalog | fitness/<name>.fitness.ts (subfolders allowed) | undefined |
| One domain | domains/Orders/fitness/<name>.fitness.ts | the Orders domain |
| One system | …/systems/checkout-system/fitness/<name>.fitness.ts | the system |
| One service | …/services/OrderService/fitness/<name>.fitness.ts | the service |
| One event, command or query | …/events/OrderCreated/fitness/<name>.fitness.ts | the message |
| One data store | …/containers/payment-database/fitness/<name>.fitness.ts | the data store (type: 'container') |
| One specific version | …/versioned/1.0.0/fitness/<name>.fitness.ts | that version |
Channels, flows, entities, data products, ADRs and diagrams work the same way. Rules:
- Only files ending in
.fitness.tsrun. Other.tsfiles (shared helpers) are never run, so fitness functions can import them. - The
fitness/folder must sit directly inside the resource's folder (beside itsindex.mdx). A.fitness.tsfile anywhere else is not run and is reported as a diagnostic. - Fitness functions in federated catalogs (
federated/…) are not run, and federation does not copyfitness/folders.
Step 4: Write the fitness function
// domains/Orders/fitness/no-shared-data-stores.fitness.ts
import { defineFitnessFunction } from '@eventcatalog/fitness';
export default defineFitnessFunction({
id: 'orders-no-shared-data-stores', // unique across the catalog
name: 'Orders services only use Orders data stores',
description: 'Reaching into another domain’s database couples the domains together.',
severity: 'error', // or 'warning'; defaults to 'error'
async run({ resource, catalog }) {
if (resource?.type !== 'domain') throw new Error('Expected to run beside a domain');
// ...read what you need from `catalog`, decide what's wrong...
return [
{
message: 'Order Service writes to payment-database, which is outside the Orders domain',
resources: [
{ type: 'service', id: 'order-service', version: '1.0.0' },
{ type: 'container', id: 'payment-database' },
],
details: 'Optional extra context',
suggestion: 'Get payment data through the Payments API or its events.',
},
]; // [] means the rule passed
},
});
Conventions when writing them:
export default defineFitnessFunction({ ... })— one function per file.runcan be sync or async.- Return violations, don't throw them. An empty array means passed. Throwing (or returning anything that isn't an array of
{ message, resources?, details?, suggestion? }) makes the function anerror, which also fails the check but means "couldn't be evaluated", not "architecture is wrong". - Narrow
resourceby itstype(resource?.type === 'domain'gives a typed Domain) and throw a clear error if the file is in the wrong place. - Use the
catalogread methods — the same names and arguments as@eventcatalog/sdk:getDomains,getServices,getEvents,getDataStores,getTeams,getGraph,getOwnersForResource,getSchemaForMessage,getResourcesByProperty, … Collection getters returnundefinedwhen there's nothing, so write(await catalog.getEvents({ latestOnly: true })) ?? []. There are no write methods, and every call returns a copy, so mutating results is harmless. Full list: references/api.md. - Read collections once and build lookups (maps or sets) instead of calling the catalog inside loops — e.g. collect every service's
sendsonce rather than callinggetProducersAndConsumersForMessageper message. - Reference resources in every violation with
{ type, id, version? }— they're linked from the violation in EventCatalog. Types:domain,system,service,agent,event,command,query,flow,channel,entity,container(dataStoreis accepted),data-product,adr,diagram,team,user. - Write messages a reader can act on: name the resources (
Order Service sends Order Amended without a channel), and put the fix insuggestion. - Shared logic goes in helper files (e.g.
fitness/helpers/catalog.ts) imported with relative paths. Import types withimport type { ReadOnlyCatalog } from '@eventcatalog/fitness'. - Keep them deterministic: no network calls, no writing files, no dependence on the current date.
Worked examples for common rules (ownership, naming, producers, circular dependencies, domain boundaries, fan-out, channels, schemas, data access): references/examples.md.
Step 5: Run it and verify it
npx eventcatalog fitness check # or: npm run fitness
npx eventcatalog fitness check --format json
The output lists every function with its result, then the violations and errors under it:
EventCatalog Fitness
✓ Every message has an owner Passed
✗ orders / Orders services only use Orders data… 1 violation
• Order Service writes to payment-database, which is outside the Orders domain
Resources: order-service (service), payment-database (container)
Suggestion: Get payment data through the Payments API or its events.
! Every event has a producer 3 warnings
3 fitness functions evaluated
Verify before handing over — a rule that can never fail is worse than none:
- The new function ran, with the status you expect. If it shows
Error, fix it (see Troubleshooting). - Its results are right. Check a few violations (and a few passes) against the catalog files by hand.
- It can fail. If it passes on the current catalog, confirm it would catch a real violation — reason it through with the user, or break a resource in a scratch copy of the catalog and run the check there. Never leave test edits in the user's catalog.
- References resolve. A reference shown as
(…, not found in the catalog)has a wrong type or id.
Exit codes: 0 everything passed or only warned, 1 a function failed or couldn't be evaluated, 2 the check itself couldn't run.
Step 6: Run it in CI
Run npx eventcatalog fitness check in the pipeline that checks catalog changes, after installing dependencies. It exits with 1 when a rule fails, which fails the job. GitHub Actions, GitLab and report-publishing examples: references/ci.md.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Function not listed at all | File doesn't end in .fitness.ts, isn't in a fitness/ folder, or is under federated/, node_modules, dist or a dot folder |
! <file>: Not run: fitness functions must be in a fitness/ folder | Move the file into a fitness/ folder at the root or directly inside a resource's folder |
No resource found for this fitness function: '<folder>' has no index.mdx or index.md | The fitness/ folder isn't directly inside a resource's folder |
… is not in a resource folder (such as domains/ or services/) | The folder holding fitness/ isn't a recognised resource folder |
The file has no default export | Add export default defineFitnessFunction({ ... }) |
Invalid fitness function: \id` must be a non-empty string, …` | Missing id/name/run, or severity isn't 'error' or 'warning' |
Another fitness function already has the id '<id>' | Ids must be unique; the first file (by path) runs, later ones error |
run() must return an array of violations … / Violation 0: … | run returned something else, or a violation is missing message or has a bad resources entry |
Cannot find module './helpers/…' | Wrong relative path to a helper — count the folders up to the catalog root |
(…, not found in the catalog) after a resource | The reference's type or id doesn't match a resource; it's kept but not linked |
Cannot find module '@eventcatalog/fitness' in the editor only | Install @eventcatalog/fitness as a dev dependency; the check itself works without it |
fitness is not a command | @eventcatalog/core is older than 5.4.0 — upgrade, or use npx @eventcatalog/cli fitness check |
Trust
Fitness functions are code from the catalog, run with the same access as any other script in the project. Only run fitness functions you trust, locally or in CI. EventCatalog never runs them in the browser, on page requests, or from federated catalogs.
Reference Files
- references/api.md —
defineFitnessFunction, the context, everycatalogmethod, violations, statuses, the JSON report, CLI options and discovery rules - references/examples.md — complete fitness functions for common rules, global and scoped, with shared helpers
- references/ci.md — running the check in GitHub Actions and GitLab, publishing the report, reading the JSON output