The Unit of Adoption Must Equal the Unit of Dependency
A consumer should be able to take the smallest thing it wants and pay only for what that thing imports.
Context
A consumer wanted one test helper: a check asserting the authorization posture of every route from its metadata. Test-only, no runtime footprint, and a strict capability upgrade over the consumer's hand-maintained equivalent, the shared version enumerates the real routes, so a handler nobody remembered to add still fails the check.
Adoption was abandoned. The helper lives in a package declaring four framework dependencies, one of which is a composition vocabulary the consumer does not use at all. The helper's own file imports none of them. Measured across that package: of fourteen files, eight import nothing from the framework, and the composition vocabulary is pulled in by exactly one file.
Decision
State the rule the packaging failed to express: a capability is independently adoptable only when its package dependency set is no larger than its code dependency set. The practical test, if a consumer adopting a capability must declare a dependency it will never import, that capability is not independently adoptable.
The dependency law was confirmed sound. The boundaries were found not to express it. Keeping those two findings separate is what stopped this becoming a redesign.
Options Weighed
- Accept the extra dependencies and adopt anywayRejected
Declines to fix the cause, and adds a hazard: production code importing from a development dependency builds locally and fails in a production install.
- Move the file to a dependency-free homeRejected
Correct in substance, but it creates a package boundary as a side effect of an extraction. Boundaries are a decision, not an implementation convenience.
- Split the package nowRejected
Same objection, larger blast radius, and chosen from a single data point.
- State the rule now; decide the boundary separatelyChosen
One data point sets a direction, not a layout. The rule is what generalises; the layout needs more evidence.
Consequences
- The originally wanted adoption stays blocked, deliberately, and is recorded as blocked rather than quietly worked around.
- The rule has a verifiable form: compare each package's declared dependencies against those its source actually imports. Today that check would pass at whole-package granularity and fail per file, which is precisely the problem.
- It generalises past packaging. Any interface with consumers, a module, a service, a shared component, a process, can force people to accept more than they asked for, and the same test applies.
Cost of Not Deciding
Each blocked adoption gets solved locally, by moving whichever file happens to be in the way. Boundaries then end up set by whichever extraction hit the problem first, rather than by a decision anyone made, and every later consumer inherits that accident.
Rollback
Not applicable. No code moved; the output was the rule.
Evidence Required to Reopen
- A second blocked adoption appears, so the boundary layout is chosen from several data points rather than one.
- Before the sibling adapter acquires consumers, run the same per-file audit on it rather than assuming it has the same shape.
Learned From
Derives From· 3
Related Knowledge· 1
Version History
- v1.0.0
Accepted. Rule stated from one measured incident; the boundary layout deliberately left open.