---
title: 'Decision Making'
source: 'https://academia.sh/en/courses/architect-role/decision-making'
course: "The Architect's Role"
language: en
updated: '2026-08-23T07:01:03+00:00'
license: 'CC BY-SA 4.0'
---

# Decision Making

Making reversible and irreversible decisions with separate methods: computing reversal cost from a dependency set, splitting the same decision set into two classes by that cost, running the quick-trial and long-evaluation methods on both classes, and comparing wait time against wrong-decision cost across four policies.

The previous lesson computed a simplification's price but left one question open: can the removed
wrapper be brought back? Gathering the config reading scattered across six modules back into a
single point is not a job of the same size as removing the wrapper. Some decisions are like this;
reversing them costs many times more than making them.

This gap goes unseen most of the time in practice, because every decision is made in the same
place at the same speed. The result is a two-way waste: weeks of meetings get held for reversible
decisions, and irreversible decisions get closed in a single afternoon. What this lesson measures
is how many person-days these two mistakes cost.

## Reversal Cost Is Read From a Dependency Set

The degree to which a decision is reversible is not a feeling, it is a computation. A decision lands on a
component; reversing it requires traversing that component and every component that depends on
it. The model is the library network's software system; it is fiction, no real institution is
described.

```js
// decision/cost.mjs — reversal cost is read from a set of dependencies; this is a fictional model
// DEPENDENCY[x] = the components x uses. Reversal requires traversing the changed component and
// every component that depends on it (AP7).
export const DEPENDENCY = {
  "data-store": [],
  "loan-core": ["data-store"],
  "membership": ["data-store"],
  "catalog-gateway": ["data-store"],
  "reservation": ["loan-core"],
  "reporting": ["data-store", "loan-core"],
  "payment-gateway": ["loan-core"],
  "notification": ["reservation"],
  "matching-pipeline": ["catalog-gateway", "data-store"],
  "branch-client": ["loan-core", "reservation", "catalog-gateway"],
};

// AP7 coefficients (person-days). Traversal per component 2; if the written records' format
// changes, migration is 15; if an exposed interface is versioned, coordination is 20.
export const COEFFICIENT = { component: 2, migration: 15, contract: 20 };

export const DECISION = [
  { name: "loan-record-format", component: "data-store", formatChange: true, external: false },
  { name: "branch-client-cache", component: "branch-client", formatChange: false, external: false },
  { name: "catalog-gateway-version", component: "catalog-gateway", formatChange: false, external: true },
  { name: "notification-text-template", component: "notification", formatChange: false, external: false },
  { name: "reporting-query-plan", component: "reporting", formatChange: false, external: false },
  { name: "loan-rule-engine", component: "loan-core", formatChange: false, external: false },
  { name: "reservation-ordering-rule", component: "reservation", formatChange: false, external: false },
  { name: "payment-interface-version", component: "payment-gateway", formatChange: false, external: true },
];

export function reached(component) {
  const set = new Set([component]);
  let grew = true;
  while (grew) {
    grew = false;
    for (const [x, uses] of Object.entries(DEPENDENCY))
      if (!set.has(x) && uses.some((y) => set.has(y))) { set.add(x); grew = true; }
  }
  return set;
}

export const reversalCost = (k) => reached(k.component).size * COEFFICIENT.component +
  (k.formatChange ? COEFFICIENT.migration : 0) + (k.external ? COEFFICIENT.contract : 0);
```

The computation has three components, and each counts a different fact. The count of reached
components is read from the graph and depends on where the decision lands. If the record format
changed, reversal concerns not only the code but the written records too; this share stands there
even without touching code. If an exposed interface has been versioned, reversal cannot be done
unilaterally, it requires coordination with the other side.

## Two Classes

```js
// decision/classify.mjs — reversal cost is computed, decisions are split into two classes
import { DECISION, reached, reversalCost } from "./cost.mjs";

// AP8: the threshold is the capacity set aside for reversal in one release cycle: 10 person-days.
const THRESHOLD = 10;
console.log("decision                    | component        |   reached | migr |  ext | reversal cost");
console.log("----------------------------|------------------|-----------|------|------|--------------");
for (const k of DECISION)
  console.log(`${k.name.padEnd(27)} | ${k.component.padEnd(16)} | ` +
    `${String(reached(k.component).size).padStart(9)} | ${(k.formatChange ? "yes" : "-").padStart(4)} | ` +
    `${(k.external ? "yes" : "-").padStart(4)} | ${String(reversalCost(k)).padStart(13)}`);

const classOf = (k) => (reversalCost(k) <= THRESHOLD ? "reversible" : "irreversible");
const group = { reversible: [], irreversible: [] };
for (const k of DECISION) group[classOf(k)].push(k);
console.log(`\nthreshold ${THRESHOLD} person-days (AP8)`);
for (const [name, list] of Object.entries(group))
  console.log(`  ${name.padEnd(15)}: ${list.length} decisions, costs ` +
    `${list.map(reversalCost).join(", ")} (total ${list.reduce((t, k) => t + reversalCost(k), 0)})`);
```

```
decision                    | component        |   reached | migr |  ext | reversal cost
----------------------------|------------------|-----------|------|------|--------------
loan-record-format          | data-store       |        10 |  yes |    - |            35
branch-client-cache         | branch-client    |         1 |    - |    - |             2
catalog-gateway-version     | catalog-gateway  |         3 |    - |  yes |            26
notification-text-template  | notification     |         1 |    - |    - |             2
reporting-query-plan        | reporting        |         1 |    - |    - |             2
loan-rule-engine            | loan-core        |         6 |    - |    - |            12
reservation-ordering-rule   | reservation      |         3 |    - |    - |             6
payment-interface-version   | payment-gateway  |         1 |    - |  yes |            22

threshold 10 person-days (AP8)
  reversible     : 4 decisions, costs 2, 2, 2, 6 (total 12)
  irreversible   : 4 decisions, costs 35, 26, 12, 22 (total 95)
```

Four of the eight decisions are **reversible decisions**, four are **irreversible decisions**, and
95 of the total reversal cost — 88.8% — is concentrated in the four decisions. The distribution of
the numbers is also instructive: `payment-interface-version` lands on just one component, its
reached count is 1, but because it versions an exposed interface, its cost is 22.
`loan-rule-engine` spreads across six components but its reversal cost is 12. That is, "how
many places it touches" alone does not determine the class; what most often makes a decision
irreversible is not the number of places it touches, but the promise it makes outward or the
record it writes.

## Two Methods, Two Classes

The comparison runs two decision methods on the same decision set. **Quick trial** makes the
decision in two days and has a high rate of turning out wrong; **long evaluation** waits fifteen
days and has a low rate of turning out wrong. The two methods produce two separate costs: every
day waited writes a delay cost, and every decision that turns out wrong writes its reversal cost.

```js
// decision/method.mjs — two decision methods on two classes: wait time and wrong-decision cost
import { DECISION, reversalCost } from "./cost.mjs";

// AP9: quick-trial waits 2 days and 35% of its decisions get reversed; long-evaluation
// waits 15 days and 10% get reversed. Each day waiting writes 0.4 person-days of delay cost.
const METHOD = {
  "quick-trial": { wait: 2, wrong: 0.35 },
  "long-evaluation": { wait: 15, wrong: 0.10 },
};
const DAILY = 0.4, THRESHOLD = 10;
const expected = (m, totalReversal, count) =>
  m.wait * count * DAILY + m.wrong * totalReversal;

const group = { reversible: [], irreversible: [] };
for (const k of DECISION) group[reversalCost(k) <= THRESHOLD ? "reversible" : "irreversible"].push(k);

console.log("class           | decisions | reversal cost |  quick-trial |  long-evaluation | winner");
console.log("----------------|-----------|---------------|--------------|------------------|-------");
for (const [name, list] of Object.entries(group)) {
  const t = list.reduce((s, k) => s + reversalCost(k), 0);
  const q = expected(METHOD["quick-trial"], t, list.length);
  const l = expected(METHOD["long-evaluation"], t, list.length);
  console.log(`${name.padEnd(15)} | ${String(list.length).padStart(9)} | ${String(t).padStart(13)} | ` +
    `${q.toFixed(2).padStart(12)} | ${l.toFixed(2).padStart(16)} | ${q < l ? "quick" : "long"} (diff ${Math.abs(q - l).toFixed(2)})`);
}

const quick = METHOD["quick-trial"], long = METHOD["long-evaluation"];
const breakeven = (long.wait - quick.wait) * DAILY / (quick.wrong - long.wrong);
console.log(`\nmethod breakeven point: once reversal cost passes ${breakeven.toFixed(1)} person-days` +
  ` long-evaluation gets cheaper`);
console.log(`class threshold (AP8) ${THRESHOLD}, breakeven point ${breakeven.toFixed(1)}: the two numbers are not the same`);

const POLICY = {
  "always-quick": () => "quick-trial",
  "always-long": () => "long-evaluation",
  "by-threshold": (k) => (reversalCost(k) <= THRESHOLD ? "quick-trial" : "long-evaluation"),
  "by-breakeven": (k) => (reversalCost(k) <= breakeven ? "quick-trial" : "long-evaluation"),
};
console.log("\npolicy          |   wait (days) | expected cost (person-days)");
console.log("----------------|---------------|----------------------------");
for (const [name, choose] of Object.entries(POLICY)) {
  let days = 0, cost = 0;
  for (const k of DECISION) {
    const m = METHOD[choose(k)];
    days += m.wait;
    cost += m.wait * DAILY + m.wrong * reversalCost(k);
  }
  console.log(`${name.padEnd(15)} | ${String(days).padStart(13)} | ${cost.toFixed(2).padStart(27)}`);
}
```

```
class           | decisions | reversal cost |  quick-trial |  long-evaluation | winner
----------------|-----------|---------------|--------------|------------------|-------
reversible      |         4 |            12 |         7.40 |            25.20 | quick (diff 17.80)
irreversible    |         4 |            95 |        36.45 |            33.50 | long (diff 2.95)

method breakeven point: once reversal cost passes 20.8 person-days long-evaluation gets cheaper
class threshold (AP8) 10, breakeven point 20.8: the two numbers are not the same

policy          |   wait (days) | expected cost (person-days)
----------------|---------------|----------------------------
always-quick    |            16 |                       43.85
always-long     |           120 |                       58.70
by-threshold    |            68 |                       40.90
by-breakeven    |            55 |                       38.70
```

The first table is the lesson's core. The same two methods, with the same measure, give opposite
results across the two classes. In the reversible class, quick trial is 17.80 person-days cheaper;
in the irreversible class, long evaluation is 2.95 person-days cheaper. Neither method is better
than the other; a method mismatched to its class is bad.

The magnitude of the two gaps is not equal either. The wrong method's penalty is 17.80 for
reversible decisions and 2.95 for irreversible ones. That is, in this model, the cost of doing
long evaluation for a reversible decision is larger than the cost of quick trial for an
irreversible decision — because long evaluation's delay cost is paid for every decision, while
reversal cost is paid only if the decision turns out wrong.

## The Threshold's Source, Again

The lower table runs four policies on the same eight decisions. The `always-long` policy is both
the most expensive and the slowest: 58.70 person-days and 120 days of waiting. `always-quick` is
the fastest, at 16 days, but its cost is higher than the policies that split by class. Splitting
the two classes shortens the wait time by 52 days relative to `always-long`, while also dropping
the cost by 17.80 person-days.

But there is a mismatch between the two thresholds, and this is where the **threshold's source**
measure from the first lesson shows up here. The class threshold was taken as 10 person-days; its
source was the capacity set aside for reversal in one release cycle. The method's breakeven point,
however, is 20.8 person-days: this number was not chosen, it was derived from the methods' wait
time and wrong rate. Because the two are not the same, the `by-threshold` policy sends the
`loan-rule-engine` decision (reversal cost 12) to long evaluation, even though that decision is
below the breakeven point. The `by-breakeven` policy sends the same decision to quick trial and
gains both 13 days and 2.20 person-days.

The rule that comes out of this is narrower and more useful than the classification itself: **the
class threshold is derived from the method, not chosen.** The moment the two decision methods'
wait time and wrong rate are written down, the threshold becomes computable. When a number coming
from somewhere else, like capacity, is used as the threshold, classification still gains
something, but it stays below what it could gain.

## Summary

- A decision's reversal cost is read from a dependency set, not from a feeling: the count of
  reached components, the migration of written records, and the coordination of an exposed
  interface.
- In the eight-decision set, 88.8% of the total reversal cost is concentrated in four irreversible
  decisions; the count of reached components alone does not determine the class.
- The same two methods gave opposite results across the two classes: quick trial is 17.80
  person-days cheaper in the reversible class, and long evaluation is 2.95 person-days cheaper in
  the irreversible class.
- When four policies are run on the same set, `always-long` came out both the most expensive and
  the slowest (58.70 person-days, 120 days); the policy that splits by class dropped to 40.90
  person-days and 68 days.
- The class threshold was 10, the method's breakeven point was 20.8; the policy that derives the
  threshold from the breakeven point came out best of the four, at 38.70 person-days and 55 days.
- The threshold is not chosen, it is computed from the two methods' wait time and wrong rate.

## Next Step

Everything measured up to this point ends at the architect's desk: weight, complexity share,
reversal cost, threshold. None of the eight decisions gets applied at that desk. The
`loan-record-format` decision's reversal cost was computed as 35 person-days, but what this
number means differs for the unit holding the budget, differs for the team that will do the
record migration, and is something else entirely for the staff member doing the work at the
branch. The computation stays the same; how the other side reads it changes. The next lesson turns
communication with non-technical stakeholders into a data structure: when the same decision is
presented to the same stakeholders with two different narrations, the questions asked and the
rounds needed for approval are counted.
