AvelonDocs

Guide 09

Bailiff

09. Bailiff

An ESLint plugin plus a CLI wrapper. Violations appear as editor squiggles, not as a code review comment somebody forgets to leave.

This is the feature that separates Avelon from a boilerplate. A template is advice. A linter is a decision.


Rules

Every rule ships with a documented reason and an autofix or codemod where one is possible. A rule you cannot explain in one sentence does not belong in the set.

RuleEnforcesLevel
no-vendor-importNo vendor SDK imported outside a driver package or avelon.config.tserror
no-cross-layerViews cannot import Models. Controllers cannot import Controllers. Models cannot import anything under Http/.error
no-orphan-queryDatabase access only in Models, Actions, Errands, and seeds. Never in a component or a page.error
no-unwardedScrivener.unwarded() only in app/Errands/ and database/seeds/error
require-wardEvery model has a warderror
no-raw-outside-driversdriver.raw() only in app/Drivers/ or behind an explicit disable commenterror
no-hand-edited-generatedAny diff to generated paths that did not come from a generatorerror
no-bun-in-core@avelon/core and @avelon/orm may not import bun:*error
no-model-across-boundaryA model instance passed to view() instead of a serializererror
require-requestA write action validates through a Requesterror
no-fake-transactionSequential writes wrapped in try/catch and named like a transactionerror
relation-depth.with() chains exceeding the configured driver's maxRelationDeptherror
no-lib-dumpingThere is no lib/. A file has a home or it does not exist.error
no-not-supported-errorThrowing on an unsupported capability instead of typing it awayerror
attribute-schema-matchModel attribute declarations match database/types.tserror
controller-thinnessA controller action past N statements suggests an Actionwarn
no-any-publicany in an exported signatureerror

Why each error rule exists

no-vendor-import is the whole driver system. One import in a controller and the abstraction is decorative.

no-cross-layer is what keeps the structure legible after the person who set it up leaves. A view importing a model is how "just this once" becomes an architecture.

no-orphan-query stops the pattern where a component fetches its own data, which is how you get forty queries on a page and no way to see them.

no-unwarded and require-ward are data leak prevention, not style. A model without a ward is a table anybody can read.

no-hand-edited-generated protects the upgrade path. A hand-edit to a generated router silently disappears at the next sync, and the person who made it will not connect the two events.

no-model-across-boundary turns a confusing serialization error at runtime into a clear one at authoring time.

no-fake-transaction is subtle and worth the rule. Code that reads as atomic and is not produces partial writes under failure, and reviewers do not catch it because it looks correct.

attribute-schema-match makes a column rename a type error rather than an incident.


Escape hatches

Every rule can be disabled inline, and every disable requires a reason:

// bailiff-disable-next-line no-raw-outside-drivers -- pgvector similarity, no IR support yet
const rows = await DB.raw().rpc('match_documents', { embedding })

A bare // bailiff-disable-next-line with no reason is itself an error.

This matters more than the rules do. A linter with no escape hatch gets disabled wholesale, and then you lose the entire thesis. A disable with a reason is a searchable list of everywhere the framework was not good enough, which is a roadmap.


Running

reeve bailiff              # all rules
reeve bailiff --fix        # autofixable only
reeve bailiff --explain no-orphan-query

A default scan skips node_modules, .next, dist, archive/, and spikes/. Historical archive trees and throwaway spikes are not the product corpus; application and library code still has to obey the rules. Tests under @avelon/core and @avelon/orm may import bun:test because they are not the runtime graph. bun:* in those packages' src/ remains an error.

Ships in reeve doctor and in the default CI workflow. Editor integration is the ESLint plugin, so no separate extension is required.


Configuration

Rules are on by default. A project may downgrade a rule to warn or disable it in avelon.config.ts, and reeve doctor prints which rules are not at their default so the drift is visible rather than forgotten.

Adding a project-specific rule uses the same plugin API the built-in rules use.