# Fail at the boundary

> A failure should surface at the boundary where it happens, as shown in masked and surfaced].

Page: Methodology · Build
Canonical: https://banes-lab.com/disciplined-methodology/build#fail-at-the-boundary

This section is stop 38 of 102 in the learning route. Previous: [23 - The filesystem is the architecture](https://banes-lab.com/disciplined-methodology/build/the-filesystem-is-the-architecture.md). Next: [25 - Placement is a grammar](https://banes-lab.com/disciplined-methodology/build/placement-is-a-grammar.md).

A failure should surface at the boundary where it happens, as shown in [H1·a masked and surfaced](https://banes-lab.com/disciplined-methodology/build#fail-at-the-boundary-panel-a). The principle is [fail fast](https://banes-lab.com/records/arch/fail-fast.md): no default, no fallback and no second path carries on as if nothing went wrong, and [H1·b the debt shapes](https://banes-lab.com/disciplined-methodology/build#fail-at-the-boundary-panel-b) shows what replaces each of them. The architecture page gives errors their place in the language under [execution joins the halves](https://banes-lab.com/software-architecture/principles/execution-joins-the-halves.md). This section covers the practice at the boundary, and the pairs below are derived in [never and always](https://banes-lab.com/software-architecture/decay/never-and-always.md).

### Loud at the boundary

Code that handles every edge case by carrying on hides the one case that should have stopped it. A missing secret falls back to a placeholder and the deploy succeeds. The service starts talking to nothing, and the first sign is a customer reporting it. A fallback turns a loud failure into a quiet wrong answer, and a quiet wrong answer costs more than any crash.

For this reason I treat errors as part of the language: a failure surfaces where it occurs, and no default is allowed to mask it. Fallbacks, dual paths and silent defaults are refused, rather than kept as a safety net. In practice, the code fails at the first point where a precondition does not hold, and the error says what was expected and what was found. A default value is refused for anything the configuration should have supplied, and so is a second path that carries on when the first one cannot. Replaced code is deleted in the same edit rather than marked, because a marker is a second path with a label on it.

To check this, remove one required input and run. The run should stop at the boundary that needed the input and name it; a run that continued has a fallback somewhere. Fail fast applies to the boundaries of your own system. A surface a visitor meets still gets [graceful degradation](https://banes-lab.com/records/arch/graceful-degradation.md), a real page rather than a bare error, and the failure behind it is logged where you will see it.

The debt shapes are named as pairs rather than as a list of prohibitions, because each pair states what to do instead. A deprecation marker, a tombstone or a compatibility shim is how [lava flow](https://banes-lab.com/records/arch/lava-flow.md) and [zombie code](https://banes-lab.com/records/arch/zombie-code.md) begin, and its pair is explicit removal in the same edit. Every write is checked for those markers before it lands, so only living code on a single forward path remains; the accounting behind the pairs is described in [debt and leverage](https://banes-lab.com/software-architecture/decay/debt-and-leverage.md).

An environment variable never carries a fallback value, and a missing one fails at startup with its name; a placeholder there is a [hidden side effect](https://banes-lab.com/records/arch/hidden-side-effect.md) waiting for production. A guard that fails open is itself a defect, because a guard exists to stop a state, and a guard that lets the state through on error has stopped nothing. That is why [secure by default](https://banes-lab.com/records/arch/secure-by-default.md) is the same rule seen from the [security core](https://banes-lab.com/records/layer/security-core.md). A boolean flag is written as an explicit positive test rather than a negated default, so an absent flag hides the feature rather than switching it on by accident.

H1·a masked and surfaced

```javascript
const masked = env.HOST ?? "localhost";

const surfaced = env.HOST ?? fail("HOST is not set; the deploy needs a droplet");
```

H1·b the debt shapes

```mermaid
flowchart TB
subgraph never["Never · debt"]
shortcut["a shortcut"]
fallback["a fallback"]
dual["a dual path"]
deprecation["a deprecation marker"]
fornow["a for-now"]
optional["an optional feature the system depends on"]
end
subgraph always["Always · leverage"]
constraint["a constraint"]
failfast["fail-fast"]
single["a single path"]
removal["explicit removal"]
now["now"]
mandatory["mandatory"]
end
shortcut --> constraint
fallback --> failfast
dual --> single
deprecation --> removal
fornow --> now
optional --> mandatory
```

## Links to

- [Fail Fast](https://banes-lab.com/records/arch/fail-fast.md)
- [Execution joins the halves](https://banes-lab.com/software-architecture/principles/execution-joins-the-halves.md)
- [Never and always](https://banes-lab.com/software-architecture/decay/never-and-always.md)
- [Graceful Degradation](https://banes-lab.com/records/arch/graceful-degradation.md)
- [Lava Flow](https://banes-lab.com/records/arch/lava-flow.md)
- [Zombie Code](https://banes-lab.com/records/arch/zombie-code.md)
- [Debt and leverage](https://banes-lab.com/software-architecture/decay/debt-and-leverage.md)
- [Hidden Side Effect](https://banes-lab.com/records/arch/hidden-side-effect.md)
- [Secure by Default](https://banes-lab.com/records/arch/secure-by-default.md)
- [Security Core](https://banes-lab.com/records/layer/security-core.md)

## Linked from

- [Validation gates](https://banes-lab.com/pag/validation/validation-gates.md)
- [Execution joins the halves](https://banes-lab.com/software-architecture/principles/execution-joins-the-halves.md)
- [Never and always](https://banes-lab.com/software-architecture/decay/never-and-always.md)
