# Seven controls, seven classes

> This section covers the seven controls, each of which owns a decay class, as shown in seven controls] and typed in [an anti-pattern record].

Page: Architecture · Decay
Canonical: https://banes-lab.com/software-architecture/decay#seven-controls-seven-classes

This section is stop 65 of 102 in the learning route. Previous: [01 - An anti-pattern is a decay path](https://banes-lab.com/software-architecture/decay/an-anti-pattern-is-a-decay-path.md). Next: [03 - Never and always](https://banes-lab.com/software-architecture/decay/never-and-always.md). It builds on [01 - An anti-pattern is a decay path](https://banes-lab.com/software-architecture/decay/an-anti-pattern-is-a-decay-path.md).

This section covers the seven controls, each of which owns a decay class, as shown in [B1·a seven controls](https://banes-lab.com/software-architecture/decay#seven-controls-seven-classes-panel-a) and typed in [B1·b an anti-pattern record](https://banes-lab.com/software-architecture/decay#seven-controls-seven-classes-panel-b). The seven are a boundary, a contract, an ownership, a [versioning](https://banes-lab.com/records/arch/versioning.md), an [observability](https://banes-lab.com/records/arch/observability.md), a [state isolation](https://banes-lab.com/records/arch/state-isolation.md) and an enforcement, and the [anti-pattern record](https://banes-lab.com/records/algo/architecture-anti-pattern.md) holds as its invariant that every decay path is caused by the absence of one of them. The same idea generalises past architecture, since a [taxonomy of smells](https://banes-lab.com/records/algo/smell-taxonomy.md) groups any bad practice by the control it lacks rather than by how it looks, and a catalogue organised that way answers a question a gallery of symptoms cannot, which is what to install.

### Placed by what was missing

A catalogue organised by appearance grows without bound and never says what to install. A team keeps a growing list of things that went wrong, each with its own fix, and the next failure fits none of the entries because it was filed by appearance and appears differently this time. A catalogue of symptoms grows without bound and teaches recognition, while a catalogue of absent controls stays small and teaches repair, because the number of ways to look wrong is unbounded and the number of things that can be missing is not.

For this reason seven controls own seven decay classes, and an anti-pattern is placed by the control it lacks. A catalogue of absences is kept rather than a gallery of symptoms. In practice, every anti-pattern is placed under the one control whose absence lets it start, and the catalogue is read as seven classes rather than as a list. When a new pattern appears, the first question is which of the seven was missing, and only then what it looks like, because the answer names the repair.

To check this, take any three anti-patterns you know and name the control each lacks. If two of them lack the same control, they are one class with two faces, and one check covers both. The seven controls classify absence and never presence. A tree that has all seven is not thereby well designed. It is a tree in which the decay classes have something to refuse them, and what it refuses is still decided by the principles the controls hold.

### The seven, each with its class

A missing boundary lets a foreign model leak into yours until the two are one tangled model, which shows as [boundary leakage](https://banes-lab.com/records/arch/boundary-leakage.md), [framework leakage](https://banes-lab.com/records/arch/framework-leakage.md) and the [anemic domain model](https://banes-lab.com/records/arch/anemic-domain-model.md) that follows. A missing contract lets a consumer depend on a behaviour that was never promised, an [implicit contract](https://banes-lab.com/records/arch/implicit-contract.md), so the next change breaks it silently. A missing ownership lets two parties write [shared mutable state](https://banes-lab.com/records/arch/shared-mutable-state.md), and the write that lands last wins with no report, which is the [lost update](https://banes-lab.com/records/arch/lost-update.md).

A missing versioning lets an [unversioned breaking change](https://banes-lab.com/records/arch/unversioned-breaking-change.md) ship as an ordinary one, and [schema drift](https://banes-lab.com/records/arch/schema-drift.md) follows. A missing observability lets a system run with [opaque runtime behavior](https://banes-lab.com/records/arch/opaque-runtime-behavior.md), so the first sign of an [unobservable failure](https://banes-lab.com/records/arch/unobservable-failure.md) is a customer reporting it. A missing state isolation lets a [hidden side effect](https://banes-lab.com/records/arch/hidden-side-effect.md) reach across a boundary it should not see, and [action at a distance](https://banes-lab.com/records/arch/action-at-a-distance.md) is its name once the cause can no longer be found. A missing enforcement is [manual-only governance](https://banes-lab.com/records/arch/manual-only-governance.md), a rule the whole team agreed to, decaying at the rate of attention.

### When an entry is real

Two questions decide whether a catalogue entry is real. Can the path be replayed from its first stage, so that a reader with a different tree reproduces the decay rather than recognising the picture? And does the entry name what it violates by identity, so that a finding on the [enforcement layer](https://banes-lab.com/records/layer/enforcement-core.md) resolves to the principle, its severity and the repair in one lookup?

An anti-pattern held as a [typed relationship record](https://banes-lab.com/records/algo/anti-pattern-relationship-record.md) answers both, and a smell [compiles into a rule](https://banes-lab.com/records/algo/anti-pattern-rule-compiler.md) only once it does. [Pattern cargo cult](https://banes-lab.com/records/arch/pattern-cargo-cult.md) and the [golden hammer](https://banes-lab.com/records/arch/golden-hammer.md) are what a catalogue of appearances produces, which is a shape applied because it was recognised, never because its absent control was named.

### The same classes at a model's speed

For a model-authored codebase the decay classes are the same and the rate is different. A model tends to take a shortcut as readily as a developer under a deadline, and it rarely objects to its own, so a system with an absent control decays at the model's speed rather than a team's.

A control that exists only as a reviewer's habit is absent for every change the reviewer did not see. Installing the seven as checks before the model writes anything is what makes them present for every change rather than for the ones a reviewer happened to read.

B1·a seven controls

```mermaid
flowchart LR
boundary["boundary absent · a foreign model leaks in"]
contract["contract absent · a consumer depends on a promise that was never made"]
ownership["ownership absent · two writers, the last one wins"]
versioning["versioning absent · a breaking change ships as ordinary"]
observability["observability absent · the first signal is a customer"]
isolation["state isolation absent · a side effect crosses a boundary"]
enforcement["enforcement absent · a rule decays at the rate of attention"]
decay["One decay class per control · placed by what was missing, never by how it looked"]
boundary --> decay
contract --> decay
ownership --> decay
versioning --> decay
observability --> decay
isolation --> decay
enforcement --> decay
```

B1·b an anti-pattern record

```typescript
export const CONTROLS = ["boundary", "contract", "ownership", "versioning", "observability", "state-isolation", "enforcement"] as const;
export type Control = (typeof CONTROLS)[number];

export const PROPAGATION = ["shortcut", "repetition", "normalisation", "dependency-formation", "institutionalisation", "high-cost-repair"] as const;
export type Stage = (typeof PROPAGATION)[number];

export const REMEDIATION = ["missing-control", "inverse-control", "migration", "absence-check", "prevention-gate"] as const;
export type Repair = (typeof REMEDIATION)[number];

export interface AntiPattern {
readonly id: AntiPatternId;
readonly absentControl: Control;
readonly path: readonly { readonly stage: Stage; readonly change: string }[];
readonly presentsAs: string;
readonly violates: readonly PrincipleId[];
readonly detectedBy: readonly SignalId[];
readonly repairs: readonly { readonly step: Repair; readonly action: string }[];
}
```

## Links to

- [Versioning](https://banes-lab.com/records/arch/versioning.md)
- [Observability](https://banes-lab.com/records/arch/observability.md)
- [State Isolation](https://banes-lab.com/records/arch/state-isolation.md)
- [<Architecture Anti-Pattern>](https://banes-lab.com/records/algo/architecture-anti-pattern.md)
- [Smell Taxonomy](https://banes-lab.com/records/algo/smell-taxonomy.md)
- [Boundary Leakage](https://banes-lab.com/records/arch/boundary-leakage.md)
- [Framework Leakage](https://banes-lab.com/records/arch/framework-leakage.md)
- [Anemic Domain Model](https://banes-lab.com/records/arch/anemic-domain-model.md)
- [Implicit Contract](https://banes-lab.com/records/arch/implicit-contract.md)
- [Shared Mutable State](https://banes-lab.com/records/arch/shared-mutable-state.md)
- [Lost Update](https://banes-lab.com/records/arch/lost-update.md)
- [Unversioned Breaking Change](https://banes-lab.com/records/arch/unversioned-breaking-change.md)
- [Schema Drift](https://banes-lab.com/records/arch/schema-drift.md)
- [Opaque Runtime Behavior](https://banes-lab.com/records/arch/opaque-runtime-behavior.md)
- [Unobservable Failure](https://banes-lab.com/records/arch/unobservable-failure.md)
- [Hidden Side Effect](https://banes-lab.com/records/arch/hidden-side-effect.md)
- [Action at a Distance](https://banes-lab.com/records/arch/action-at-a-distance.md)
- [Manual-Only Governance](https://banes-lab.com/records/arch/manual-only-governance.md)
- [Enforcement Core](https://banes-lab.com/records/layer/enforcement-core.md)
- [Anti-Pattern Relationship Record](https://banes-lab.com/records/algo/anti-pattern-relationship-record.md)
- [Anti-Pattern Rule Compiler](https://banes-lab.com/records/algo/anti-pattern-rule-compiler.md)
- [Pattern Cargo Cult](https://banes-lab.com/records/arch/pattern-cargo-cult.md)
- [Golden Hammer](https://banes-lab.com/records/arch/golden-hammer.md)

## Linked from

- [The gate holds the line](https://banes-lab.com/disciplined-methodology/build/the-gate-holds-the-line.md)
