# A tension has a mechanism

> This section covers how two records that pull against each other on one construct are resolved.

Page: Architecture · Principles
Canonical: https://banes-lab.com/software-architecture/principles#a-tension-has-a-mechanism

This section is stop 62 of 102 in the learning route. Previous: [06 - The structural domain](https://banes-lab.com/software-architecture/principles/the-structural-domain.md). Next: [08 - Separate, trade, or mitigate](https://banes-lab.com/software-architecture/principles/separate-trade-or-mitigate.md). It builds on [01 - Principles are typed](https://banes-lab.com/software-architecture/principles/principles-are-typed.md), [04 - Computation and resource](https://banes-lab.com/software-architecture/principles/computation-and-resource.md), [16 - When rules collide](https://banes-lab.com/disciplined-methodology/plan/when-rules-collide.md).

This section covers how two records that pull against each other on one construct are resolved. Every resolution the canon holds is [readable as a record](https://banes-lab.com/records/tension/decentralization-single-source-of-truth.md), and where no record exists the mechanism is derived from the kinds and scopes of the two sides, in the order shown in [G1·a recorded or derived](https://banes-lab.com/software-architecture/principles#a-tension-has-a-mechanism-panel-a) and over the grid shown in [G1·b kinds against scopes](https://banes-lab.com/software-architecture/principles#a-tension-has-a-mechanism-panel-b). A tension held as data, in the shape typed in [G1·c a resolution record](https://banes-lab.com/software-architecture/principles#a-tension-has-a-mechanism-panel-c), is consulted before the collision happens, while one held in prose is rediscovered by collision.

### Recorded, never remembered

Principles that appear to conflict are resolved case by case, and case-by-case resolution is a different rule in every case. A review says the config must [fail fast](https://banes-lab.com/records/arch/fail-fast.md), the next review says the parser must tolerate bad input, both cite a principle, both are right, and the code ends up doing neither consistently. A principle stated without its scope reads as universal, so the moment two universal statements meet on one construct one of them has to lose, and the loser is chosen by the reviewer.

For this reason an apparent conflict has a mechanism that follows from the kinds and scopes on each side, and the resolution is recorded rather than remembered. A recorded resolution is looked for before one is derived, and an exemption is refused as a third option, because one side winning for a reason that was never written down is a rule with no scope. In practice, each side is classified before anything is resolved, as a principle, a quality, a metric or a cost, together with the scope it holds in. A derived resolution is recorded with its two records by identity, the mechanism, the scope each side holds in and the rule in one sentence, so the next reader finds a rule rather than a memory.

To check this, take any construct where two things seemed to collide and name the kind and scope of each. If a resolution is recorded, it is settled, and if a mechanism follows from the kinds and scopes, writing it down settles it. If the construct still sits on both sides, it is two constructs and needs splitting. A tension is a pair the canon relates by a tension edge, which is a different relation from a conflict. A conflict points from a principle to the anti-pattern that negates it, and it carries no resolution because one side is simply refused. Two rules that merely differ in strictness are not a tension, and neither is a rule that does not apply to a construct. The first is one rule with a scope, and the second is a classification question.

### The derivation

The resolution is typed for the same reason a principle is. It names its two records by identity, the mechanism from the closed set of three, the scope each side holds in, and the rule in one sentence. The derivation has the shape of every default that must be safe when the case has not been thought about.

A recorded resolution wins outright, since a developer has already decided. Failing that, two principles that hold in different scopes separate, because each can hold whole on its own side. Everything else, a quality on either side or two principles that share one scope, falls to a trade-off, because the only thing that can be said about an undecided pair is that it has to be measured. Mitigation is never derived, because a discriminator is a judgement, and a judgement the data does not carry explicitly does not exist.

### What a check does with it

Recording it that way lets a check resolve a finding to its side. A validator that fires on a [fallback pattern](https://banes-lab.com/records/arch/fallback-pattern.md) in a resource path reaches the fail fast side, and one that fires on an unmarked uncertainty in a computation reaches the explicit-invalidity side, and neither has to know the other exists. The canon's [resolution contract](https://banes-lab.com/records/algo/conflict-and-tension-resolution.md) states the same obligation from the other direction. Architecture is trade-off governance as much as principle application, and the contract's policy admits an override only as a recorded decision beside the mitigations and the documented trade-offs.

Every resolution the canon currently holds is listed on [the schema tab](https://banes-lab.com/ontology/schema/the-resolutions.md), with its mechanism, its two scopes and its rule.

### Two checks on one construct

Where two enforced checks conflict on one construct, neither is satisfied by violating the other and neither is disabled. The construct is reshaped into the single form that satisfies every rule, and where that form is not obvious the question goes to the developer who owns the rules. An exclusion added to make a check pass is the exemption this whole section refuses, arriving through the tooling instead of the review. Two such collisions are walked in [when rules collide](https://banes-lab.com/disciplined-methodology/plan/when-rules-collide.md) on the methodology page.

G1·a recorded or derived

```mermaid
flowchart TB
pair["Two records pull against each other on one construct"]
exemption["An exemption · one wins here, for a reason never written down"]
recorded{"Is a resolution recorded for the pair?"}
kinds{"What kind of thing is on each side, and in which scope?"}
separate["Two principles in different scopes · each holds whole in its own"]
trade["Anything else · a quality on either side, or two principles in one scope · measure, choose an operating point, write it down"]
mitigate["Recorded with a discriminator · a rule names what tells the two apart"]
pair -. the tempting answer .-> exemption
pair --> recorded
recorded -- yes --> mitigate
recorded -- no --> kinds
kinds -- principle and principle, scopes differ --> separate
kinds -- otherwise --> trade
```

G1·b kinds against scopes

```mermaid
quadrantChart
title Where a pair lands decides its mechanism
x-axis one scope --> two scopes
y-axis a quality on one side --> two principles
quadrant-1 separate by scope
quadrant-2 trade at a measured point
quadrant-3 trade at a measured point
quadrant-4 trade at a measured point
statelessness against state before mutation: [0.85, 0.85]
single source of truth against decentralization: [0.75, 0.9]
fail fast against graceful degradation: [0.15, 0.8]
consistency against availability: [0.8, 0.2]
encapsulation against debuggability: [0.3, 0.15]
backpressure against throughput: [0.2, 0.3]
```

G1·c a resolution record

```typescript
export type Mechanism = "scope-separation" | "irreducible-tradeoff" | "mitigation";

export interface Resolution {
readonly a: RecordId;
readonly b: RecordId;
readonly mechanism: Mechanism;
readonly scopeA: LayerId;
readonly scopeB: LayerId;
readonly rule: string;
}

export const mechanismOf = (a: Record, b: Record, recorded: ReadonlyMap<PairKey, Resolution>): Mechanism => {
const explicit = recorded.get(pairKey(a.id, b.id));
if (explicit !== undefined) {
return explicit.mechanism;
}
const separable = a.kind === "principle" && b.kind === "principle" && a.layer !== b.layer;
return separable ? "scope-separation" : "irreducible-tradeoff";
};
```

## Links to

- [Single Source of Truth / Decentralization](https://banes-lab.com/records/tension/decentralization-single-source-of-truth.md)
- [Fail Fast](https://banes-lab.com/records/arch/fail-fast.md)
- [Fallback Pattern](https://banes-lab.com/records/arch/fallback-pattern.md)
- [Conflict and Tension Resolution](https://banes-lab.com/records/algo/conflict-and-tension-resolution.md)
- [The resolutions](https://banes-lab.com/ontology/schema/the-resolutions.md)
- [When rules collide](https://banes-lab.com/disciplined-methodology/plan/when-rules-collide.md)

## Linked from

- [Orchestration invariants](https://banes-lab.com/pag/orchestration/orchestration-invariants.md)
- [When rules collide](https://banes-lab.com/disciplined-methodology/plan/when-rules-collide.md)
- [Separate, trade, or mitigate](https://banes-lab.com/software-architecture/principles/separate-trade-or-mitigate.md)
