Case file — A3

Stable Dependencies Principle

Volatile depends on stable, never the reverse.

Dependencies have a direction. Volatile code depends on stable code; never the reverse. Reverse the arrow and the volatile core destabilises everything that imports it.

ByAdam LewisPublished3 May 2026Reading12 minVersionv1.0ConfidenceHigh
§0b

Opinion

Most architecture diagrams I've been handed in my career are lies because they redraw the import graph the team wishes it had, not the one the build is actually compiling. The Stable Dependencies Principle is the rule that puts the diagram and the graph in the same direction. When a heavily-imported file imports a barely-imported one, that barely-imported file's next change ripples through the graph in ways the team can't see until CI goes red.1Robert C. MartinClean Architecture (Pearson, 2017), Ch. 14 “Component Coupling”. The single load-bearing chapter for SDP, SAP, and ADP together — the Component Coupling triad.

The reason it gets confused with A11 Dependency Inversion is that they live in adjacent rooms. DIP is a class-level rule about what kind of thing the code depends on: an abstraction, not a concrete. SDP is a component-level rule about which direction the import points: from less-stable to more-stable. They compose: SDP plus the Stable Abstractions Principle gives Martin's “stable abstractions at every scale” argument, which is DIP scaled to packages.2Martin Fowler“DIP in the Wild” (martinfowler.com, 2014). Fowler's framing of what DIP is and is not — useful for showing that SDP and DIP are siblings (different scale, different object) rather than the same rule under different names. Different scales, different objects, same family.

The lever that makes SDP visible is the import graph. Without it the rule is a vibe; with it it is a build failure. A2 Three-Tier Hoisting is already SDP under another name: the generic/product/ → module gradient is a stability gradient by construction, and the upward-only import rule is exactly “volatile depends on stable” expressed in file paths.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

Volatile depends on stable, never the reverse. Read every import as an arrow — arrows point from less-stable code (high I = Ce / (Ca + Ce)) toward more-stable code (low I). When a stable file grows an import on a volatile one, invert the dependency through an interface or move the volatile thing.

/tenet/stable-dependencies-principle/A3
§0c

AI eyes only

Rule: volatile depends on stable. Imports flow toward stability. Never the reverse.

Reject: stable libraries importing from volatile features. Reject: utility files importing domain types. Reject: lib/generic/ importing from lib/product/.

Generate: when a stable file needs a volatile detail, invert via interface. The volatile side implements the interface; the stable side depends on the abstraction.

Diagnostic: list the imports of the file you are editing. If any target changes more often than this file, invert the dependency before you ship.

§0d

Why?

  • The import graph stops being a vibe. With SDP enforced, the direction of every edge is a decision the team can read in the build, not an emergent property of who edited which file last.
  • Stability becomes countable: I = Ce / (Ca + Ce). Reviewers stop arguing about whether a file “feels stable” and start asking the metric. The conversation shrinks to the data.
  • Caps the blast radius of a change to a stable file. When a low-I file changes, the team knows by construction which high-I leaves will recompile and which won't. Surprises in CI become almost rare.
  • Pairs cleanly with A11 Dependency Inversion: SDP picks the direction; DIP picks the shape (abstraction over concrete). Together they give Martin's “stable abstractions at every scale” argument its full form.
  • Falls out of A2 Three-Tier Hoisting for free. The generic/product/ → module gradient is a stability gradient by construction; A2's upward-only import rule is SDP in folder form.
  • Surfaces the deprecation conversation early. A file whose I climbs over time is one whose importers are moving away from it — a measurable signal that a v2 is overdue, without waiting for the team to notice in retro.
  • Coding agents reach for sideways imports and inheritance because the training data is full of both. The upward-only direction rule forces them through composition or abstraction — the same lever that protects human reviewers also protects the agent from itself.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

Robert C. Martin published the metrics that anchor SDP in “OO Design Quality Metrics: An Analysis of Dependencies” in 1994.5Robert C. Martin“OO Design Quality Metrics: An Analysis of Dependencies” (1994). The paper that defined Ca, Ce, I, A, and D. The metrical anchor for SDP — the I metric is from this paper, not the 2017 book. Afferent coupling (Ca), efferent coupling (Ce), Instability (I = Ce / (Ca + Ce)), Abstractness (A), and the Distance from the Main Sequence (D) all originate there. The 1994 paper was a quantitative argument: package-level health could be measured the way function-level complexity could be measured, and the measurements predicted where the next regression would land.

Martin formalised SDP in the 1996 C++ Report piece “Granularity” and in the package-design chapters of Agile Software Development: Principles, Patterns, and Practices (2002). The single load-bearing line: “Depend in the direction of stability.”6Robert C. Martin“Granularity”, C++ Report (1996), and Agile Software Development (2002). The line is single-sentence: depend in the direction of stability. The 1996 piece is where the package-coupling chapter from the 2002 book was first drafted. The chapter pairs SDP with its sibling SAP (Stable Abstractions Principle) so that the most-stable packages are also the most abstract — otherwise the stable parts of the system become the rigid parts.

The book-length restatement is Chapter 14 of Clean Architecture (Pearson, 2017).1Robert C. MartinClean Architecture (Pearson, 2017), Ch. 14 “Component Coupling”. The single load-bearing chapter for SDP, SAP, and ADP together — the Component Coupling triad. Martin pairs SDP with the Acyclic Dependencies Principle (ADP) and SAP into a single Component Coupling story: the package graph must be a DAG, the edges must point toward stability, and the most-stable packages must be the most abstract. ADP catches the cycles; SDP catches the bad direction; SAP catches the rigid stable thing.

The instinct is older than the metric. Larry Constantine's coupling hierarchy (1968, published 1974 with Stevens and Myers in “Structured Design”) named direction-of- coupling decades before Martin gave the package version a number.7Stevens, Myers & Constantine“Structured Design”, IBM Systems Journal 13(2), 1974. The coupling/cohesion taxonomy that predates SDP by two decades; direction-of-coupling already named, just without the metric. Meilir Page-Jones's connascence taxonomy (1992, 1996) gave the same instinct a more granular vocabulary: connascence of name is weak and local, connascence of identity is strong and dynamic, and the rule is to push every coupling toward the weaker, more local end — which, at the package level, is exactly what SDP's direction rule produces.8Meilir Page-JonesWhat Every Programmer Should Know About Object-Oriented Design (Dorset House, 1995/1996), and the 1992 CACM paper. The connascence taxonomy gives the package-direction instinct a more granular vocabulary; SDP is one rule inside the larger coupling story.

§2

Quotes

Depend in the direction of stability.

Robert C. Martin · Clean Architecture (2017), Ch. 14

I = Ce / (Ca + Ce). This metric has the range [0, 1]. I = 0 indicates a maximally stable category. I = 1 indicates a maximally unstable category.

Robert C. Martin · OO Design Quality Metrics (1994)

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviours of your system will be depended on by somebody.

Hyrum Wright · Hyrum's Law

Dependencies are the primary source of complexity, because they make it hard to modify the system: a change in one place may force changes in other places.

John Ousterhout · A Philosophy of Software Design (2018)
§3

Evidence

Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Robert C. Martin · 1994
    The paper that defined Ca, Ce, I, A, and D. Argued package-level health is countable, not subjective. The metrical anchor for SDP and SAP both.
  2. 02
    Robert C. Martin · 2017
    The book-length restatement. Pairs SDP with the Acyclic Dependencies Principle and the Stable Abstractions Principle into a single Component Coupling story; argues the package graph must be a DAG with edges pointing toward stability.
  3. 03
    Robert C. Martin · 2002–
    Martin's online compendium of the SOLID + package-design principles. The single page where SDP, SAP and ADP are listed alongside SOLID — useful for showing they belong in the same family.
  4. 04
    WikiWikiWeb contributors · 2003–
    The community wiki entry that distils SDP into one paragraph and links to its sibling rules. Useful as a beginner-readable pointer; the discussion thread captures the SDP-vs-DIP confusion in real time.
  5. 05
    Agile in a Flash · 2009
    Cohort summary of SDP + SAP + ADP for the Agile-in-a-Flash card series. The single-card framing forces the principle to fit on an index card; useful for the elevator-pitch version.

Sixteen sources, three stances. The supporters cluster on Martin's component-coupling canon: the 1994 metrics paper, the 2017 Clean Architecture chapter, plus the wiki and Agile-in-a-Flash distillations. The qualifiers further down carry the harder reading: stability is one coupling axis among several, and the metric only earns its keep when the team agrees what “stable” means in their context. The opposers carry the steelman the reply has to address: any sufficiently-used interface becomes its own promise, regardless of which way the arrows point.

§4

Examples

Viewing: TypeScript.
Avoid
Filelib/generic/string/to-slug.ts
// Before: lib/generic/ imports from lib/product/. The arrow points the wrong way.import { Tenet } from "@/lib/product/tenet/tenet.types";export function toSlug(tenet: Tenet): string {  return tenet.title.toLowerCase().replace(/\s+/g, "-");}// any Tenet field rename now ripples through every to-slug caller.
Prefer
Filelib/generic/string/to-slug.ts
// After: generic takes a primitive. The Tenet-aware caller composes the two.// lib/generic/string/to-slug.ts (no upward import):export function toSlug(value: string): string {  return value.toLowerCase().replace(/\s+/g, "-");}// lib/product/tenet/get-tenet-slug.ts composes the two:import { toSlug } from "@/lib/generic/string/to-slug";export const getTenetSlug = (tenet: Tenet): string => toSlug(tenet.title);
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
import/no-restricted-pathseslint-plugin-importany import edge that points from a stable tier to a volatile tier — a generic file growing an import on a product file, or a product file growing an import on an app route.
import/no-cycleeslint-plugin-importimport cycles between any two packages — the symptom that two files are at the same stability tier and have grown into each other rather than past each other.
validate / metrics outputdependency-cruiserthe same upward-only contract as a standalone CI step, plus per-package Ca/Ce/I/A/D metrics — the numbers SDP is built on, machine-readable.
import/no-relative-packageseslint-plugin-importrelative imports across package boundaries — the import shape that quietly defeats SDP by routing around the public surface.
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';

export default tseslint.config({
  files: ['**/*.{ts,tsx}'],
  plugins: { import: importPlugin },
  rules: {
    'import/no-restricted-paths': ['error', {
      zones: [
        {
          target: './src/lib/generic',
          from: ['./src/lib/product', './src/components', './src/app'],
          message: 'volatile depends on stable, never the reverse — generic is the most stable tier.',
        },
        {
          target: './src/lib/product',
          from: ['./src/components', './src/app'],
          message: 'product is more stable than the leaves; leaves depend on product, not the reverse.',
        },
      ],
    }],
    'import/no-cycle': ['error', { maxDepth: 10 }],
    'import/no-relative-packages': 'error',
  }
});
§4c

AI rules

File.cursor/rules/a3-stable-dependencies.mdc
---
description: Prickles A3 — Stable Dependencies Principle
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---

## Prickles A3 — Stable Dependencies Principle

Volatile depends on stable, never the reverse. Read every import edge as an arrow; arrows must point from less-stable code toward more-stable code.

Stability is countable: I = Ce / (Ca + Ce). High-I files are leaves (depend on lots, depended on by few); low-I files are roots. Imports must point from high-I toward low-I.

Pair SDP with the Stable Abstractions Principle (SAP): the most-stable packages must also be the most abstract. Otherwise the stable parts of the system become the rigid parts.

Refuse to grow a stable file with an import on a volatile one. If you would, invert the dependency through an interface or move the volatile thing into a less-stable tier.

Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.

§5

Counter-argument

Counter

The strongest steelman comes from John Ousterhout combined with Hyrum's Law.3John OusterhoutA Philosophy of Software Design (2018, 2nd ed. 2021). The strongest steelman for the “module depth matters more than direction” counter; SDP's reply is that depth lives one level down from this rule and both ship. An interface with enough callers will be relied on for every observable property, not just the documented ones, which means “stable” in practice is a consequence of usage, not a property the rule can dictate. SDP says “volatile depends on stable” as if stability were an attribute of the file; in practice it is an attribute of the social contract around the file, and the metric I = Ce / (Ca + Ce) is a proxy for that contract that misses the unwritten parts. The implication: enforcing SDP on a metric that lags the social reality can produce a folder structure that looks healthy and a team that still pays for every “stable” file's every change.

§6

Counter-argument retort

Reply

Hyrum's observation is correct, and SDP doesn't pretend otherwise. The reply is that SDP is the rule for the part of stability the team controls — the import-graph direction — not for the part it doesn't. A file that's currently stable by the metric and unstable by the social contract is a deprecation problem, not an architecture one; the next move is to publish a v2 surface and migrate the callers, exactly the cycle the Stable Abstractions Principle is here to support.4Robert C. MartinAgile Software Development: Principles, Patterns, and Practices (Prentice Hall, 2002). Ch. 28 introduces SDP's sibling SAP (Stable Abstractions Principle) — the rule that stable packages should also be abstract, so they aren't rigid. SDP catches the bad direction; SAP catches the bad shape; together they cover the part of stability the build can see.

The Ousterhout depth concern is real but lives one level down from this rule. SDP tells you which direction edges should point in the package graph; module depth tells you how those packages should organise their internals.3John OusterhoutA Philosophy of Software Design (2018, 2nd ed. 2021). The strongest steelman for the “module depth matters more than direction” counter; SDP's reply is that depth lives one level down from this rule and both ship. Both rules ship; neither replaces the other. A package with the right import direction and a shallow interface is still a leaky abstraction; a package with a deep interface and a wrong import direction is a time bomb. The team needs both metrics, and the tools to read both metrics live in different files.

The genuine residue is the conflation with A11 Dependency Inversion. They're different rules at different scales: DIP is class-level (depend on abstractions, not concretes); SDP is package-level (depend on stable components, not volatile ones). The case file pins the distinction once so readers don't conflate them. SDP + SAP is closer to DIP scaled up, but it's still not the same rule — SDP is about direction, DIP is about shape.2Martin Fowler“DIP in the Wild” (martinfowler.com, 2014). Fowler's framing of what DIP is and is not — useful for showing that SDP and DIP are siblings (different scale, different object) rather than the same rule under different names. Both belong in the canon for the same reason F1 Single Responsibility (function-scale) and A4 Common Closure (package-scale) both belong: scale matters, and the same instinct earns separate rules at separate altitudes.

The novelty challenge — that this is just “import graph cleanliness” under a fancy name — misses what the metric does. Without I and Ca/Ce, “cleanliness” is a vibe. With it, “the metric drifted from 0.1 to 0.4 this quarter on the auth package” is a sentence the team can act on. The metric earns the rule.

§7

Notes

  1. [1]Robert C. MartinClean Architecture (Pearson, 2017), Ch. 14 “Component Coupling”. The single load-bearing chapter for SDP, SAP, and ADP together — the Component Coupling triad.
  2. [2]Martin Fowler“DIP in the Wild” (martinfowler.com, 2014). Fowler's framing of what DIP is and is not — useful for showing that SDP and DIP are siblings (different scale, different object) rather than the same rule under different names.
  3. [3]John OusterhoutA Philosophy of Software Design (2018, 2nd ed. 2021). The strongest steelman for the “module depth matters more than direction” counter; SDP's reply is that depth lives one level down from this rule and both ship.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-04-27