Skip to main content

Governance rules

Architecture decisions die when they only live in a wiki. ovecc lets you declare them — language-neutral, in .ovecc/config.toml — and enforces them at index time. Violations surface in violations, advise, the gate CI check, and review.

Two levels of governance

This page covers the rule level: individual pairwise prohibitions, checked at index time, in any language. Good for a handful of "X must not import Y" decisions.

For a whole intended architecture — components, allow-lists, virtual interfaces, slice isolation, capability bans, complexity budgets, and a baseline ratchet for gradual adoption — use the architecture contract in .ovecc/architecture.toml. The two coexist: rules stay language-neutral, the contract goes deeper on JavaScript/TypeScript.

Boundary rules​

Forbid (or explicitly allow) a module-to-module dependency:

[[rules.boundaries]]
name = "core must stay foundational"
source = "core"
target = "services"
allowed = false
severity = "high"

When someone adds import { recordUsage } from "../services/billing" inside src/core/db.ts:

$ ovecc violations --severity high
[High] core -> services
Rule: core must stay foundational
Type: CrossDomainDependency
Evidence: src/core/db.ts:2 (../services/billing)

And because the rule is checked per-change too, the offending PR fails gate with 1 new boundary violation(s) — the backlog never grows silently.

source and target match module names (modules are inferred from directory structure — run ovecc query "module <name>" or ovecc export graph to see yours).

Banned imports​

Ban dependencies by specifier pattern — exact, prefix*, *suffix, or *infix*:

[[rules.banned_imports]]
name = "no-lodash"
pattern = "lodash*"
message = "use es-toolkit instead"
severity = "medium"
[Medium] Banned import: lodash*
Rule: banned-import/no-lodash
Type: ForbiddenImport
Evidence: src/utils/helpers.ts:1 (lodash/groupBy)

The pattern matches the import specifier, so lodash* catches both lodash and deep imports like lodash/groupBy. Typical uses: deprecating a library, forbidding direct database-driver imports outside the data layer, banning internal packages from leaking into public modules.

Inline suppressions​

Silence one specific finding on one specific line:

const digest = createHash("md5").update(cacheKey).digest("hex"); // ovecc-ignore
// ovecc-ignore-next-line
const legacy = eval(trustedMigrationSnippet);
result = subprocess.run(cmd, shell=True) # ovecc-ignore

The finding is dropped at index time. Suppressions are themselves audited: an ovecc-ignore that no longer suppresses anything is reported as stale-suppression (low) — because a dead ignore would silently swallow the next real finding on that line. ovecc fix deletes stale suppressions for you.

The baseline ratchet​

For findings you can't fix yet, prefer the baseline over sprinkling ignores:

ovecc violations --write-baseline # accept the current findings (.ovecc/baseline.json)
ovecc violations --baseline # report only findings NOT in the baseline

Combined with --fail-on any in CI, this is a one-way ratchet: existing debt is tolerated, new debt fails the build. Re-run --write-baseline after a cleanup to shrink the accepted set.

Scoping what gets indexed​

Also in config.toml (or as flags on index):

[index]
# Extra paths to skip, on top of the built-ins (node_modules, target, dist, .venv, ...)
exclude = ["vendored", "generated"]
# Flag manifest dependencies that no indexed file imports (off by default)
detect_unused_deps = true

The full file, key by key, is documented in the configuration reference.