# Never and always

> This section covers why every avoidance rule is written as a pair, as shown in one inversion].

Page: Architecture · Decay
Canonical: https://banes-lab.com/software-architecture/decay#never-and-always

This section is stop 66 of 102 in the learning route. Previous: [02 - Seven controls, seven classes](https://banes-lab.com/software-architecture/decay/seven-controls-seven-classes.md). Next: [04 - Debt and leverage](https://banes-lab.com/software-architecture/decay/debt-and-leverage.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), [02 - Seven controls, seven classes](https://banes-lab.com/software-architecture/decay/seven-controls-seven-classes.md), [24 - Fail at the boundary](https://banes-lab.com/disciplined-methodology/build/fail-at-the-boundary.md).

This section covers why every avoidance rule is written as a pair, as shown in [C1·a one inversion](https://banes-lab.com/software-architecture/decay#never-and-always-panel-a). A rule that only says never leaves an empty action set the moment the forbidden thing is the obvious thing, and an empty action set is how the forbidden thing gets written anyway. The canon holds every inversion as a [contract with an invariant](https://banes-lab.com/ontology/algorithms/algo-domain-architectural-rules.md), and the practice for the pairs that recur most is described in [fail at the boundary](https://banes-lab.com/disciplined-methodology/build/fail-at-the-boundary.md) on the methodology page.

### Half a rule

Avoidance rules written as a list of don'ts are obeyed until the first moment the don't is convenient. A rule says no fallbacks, a value is missing at boot, the author has nowhere to go, and the fallback is written with a comment apologising for it. A prohibition without its replacement names what to avoid and not what to do, so under pressure the avoided thing is the only thing the developer or the model knows how to write.

For this reason an avoidance rule is an inversion that pairs a refused construct and the debt it borrows against with a required construct and the leverage it buys. The replacement is written before the prohibition, so the rule is never a bare never. In practice, the consequences are kept as the rule's reason, so the rule carries its own justification and is never re-argued. A check reports the refused construct where it lands and names the required one as the remediation, and what the replacement supersedes is deleted in the same change, because a marker on the old path keeps two paths alive under one label.

To check this, cover the second half of any rule you hold and ask what you would write instead. If nothing comes, the rule was a prohibition and the debt is already in the tree somewhere. An inversion governs living code on one forward path, meaning anything something else depends on, where a shortcut taken today is read as a decision by the next developer or model to find it. It says nothing about how strict a rule is, and it is not a threshold. A bound lives where the check reads it, and the inversion only says that the bound exists.

### The derivation

The generalised form is a small derivation, and it can be run on any construct in any tree. It asks what the construct borrows against and from whom, names the construct that would stand in its place, and asks what that one buys.

If the second half comes, the pair is a rule and the consequences are its reason. If it does not, the construct is either not a debt or not yet understood, and either way it is not a rule.

### One forward path

Only living code on a single forward path survives that reading. A deprecation marker, a tombstone, a compatibility shim and [zombie code](https://banes-lab.com/records/arch/zombie-code.md) are each a refused construct whose replacement is deletion in the same edit. [Backward compatibility](https://banes-lab.com/records/arch/backward-compatibility.md) is a contract a boundary declares, never a second path kept alive inside one.

An export exists only while another file imports it now, unless it is staged behind a drift check that holds it to that promise. A [utility dump](https://banes-lab.com/records/arch/utility-dump.md) and a [repository dump](https://banes-lab.com/records/arch/repository-dump.md) are the same debt at the scale of a folder, a home for constructs whose replacement was never named, and so a place where every one of them is kept.

C1·a one inversion

```mermaid
flowchart TB
refused["A construct is refused · with the debt it borrows against"]
required["Its replacement is required · with the leverage it buys"]
refused -- one inversion, two consequences --> required
check["A check reports the refused construct where it lands"]
refused -.-> check
remedy["The finding names the required construct as its remediation"]
check --> remedy
required -.-> remedy
```

## Links to

- [Architectural Rules](https://banes-lab.com/ontology/algorithms/algo-domain-architectural-rules.md)
- [Fail at the boundary](https://banes-lab.com/disciplined-methodology/build/fail-at-the-boundary.md)
- [Zombie Code](https://banes-lab.com/records/arch/zombie-code.md)
- [Backward Compatibility](https://banes-lab.com/records/arch/backward-compatibility.md)
- [Utility Dump](https://banes-lab.com/records/arch/utility-dump.md)
- [Repository Dump](https://banes-lab.com/records/arch/repository-dump.md)

## Linked from

- [Fail at the boundary](https://banes-lab.com/disciplined-methodology/build/fail-at-the-boundary.md)
