Software Architecture

A system as a graph, principles as typed records, and the predicate set that makes an architecture real when a model writes the code.

Principles

A1Principles are typed

A principle is a typed record, never a slogan. It has one canonical identity, one kind and one category, and it carries ten slots: five edge relations that name other records, and five descriptors that say what a violation looks like, what detects it, what measures it, what repairs it and what enforces it. The whole canon resolves by identity join: every edge names a record that exists, a principle or a defined term, and a resolution gate refuses a canon with a dangling edge, a duplicate identity or a kind out of range. That is what turns a list of good ideas into something a check can consult, and it is why every record this page names can be opened and walked.

A1.1Ten slots, one gate

Principles are typed and related, and a canon that does not resolve by identity is refused by its gate. Architecture principles are usually a reading list, and a reading list cannot tell you which of its entries a given change just broke.

A review cites a principle by name, a second review cites its opposite by another name, both are in the document, and nobody can show that the two were ever meant to conflict or how the conflict resolves. Prose principles cannot be joined, so nothing can compute which principle a finding violates, which repair follows, or which two principles a design has set against each other.

Type the principles, relate them by resolvable name, and derive governance by join. Give every principle one identity that nothing else carries, a kind from a closed taxonomy and a category from the data that holds it. Record its relations to the other records as names that resolve to identities, never as free prose, and record what violates it, what detects and measures a violation, the repairs that reverse it and the gates that enforce it. Attach an exemplar with a before and an after. Then prove the whole by a gate: every edge resolves, every kind is in range, every negative edge lands on an anti-pattern. Once that holds, anything that wants to say which principles govern it can say so by identity, and its governance is a join rather than a paragraph.

Take a finding from any check and resolve its canonical id. It must reach the principle, the principle's severity, its repairs and its relation graph in one lookup. A finding that reaches only a message has no canon behind it.

A canon is a vocabulary for reasoning, and it is the wrong home for a threshold or a path. A principle says that complexity is bounded; the number lives in the gate's config and the check reads it there. A canon that carries numbers is a second config, and a canon that carries paths is bound to one tree.

A1.2The five edges

Requires says a principle cannot hold without another, so a design that adopts modularity has adopted its whole closure whether it meant to or not. Reinforces says two principles hold more easily together, which is how an improvement propagates past the record it was made to. Enables says one makes another possible.

In tension with says two records pull against each other on one construct, and every such pair carries a resolution whose mechanism follows from what the two records are. Conflicts with is the one negative edge: it points from a principle to the anti-patterns that negate it, and the polarity law says it may point nowhere else.

A1.3The five descriptors

The descriptors are prose a person reads. Violated-by describes the violation, detected-by the signals that see it, measured-by the numbers that size it, refactored-by the repairs that reverse it, and enforced-by the gates that hold the line.

Severity says how strictly the principle binds and, where the record states one, the condition under which it binds that strictly. It is a routing field: it decides which handler a failure reaches, a refusal, a review note or an information line, and it never ranks one principle above another. A check still answers pass or fail; severity says what happens to the answer.

A1.4One record, walked

Modularity is a mandatory principle in the structural core. It requires high cohesion, low coupling and explicit boundaries, because a module whose insides do not belong together, or whose edges nobody drew, is not one you can replace. It reinforces separation of concerns and composability, and it enables replaceability and plugin architecture.

It is in tension with cross-cutting concerns, and that pair carries a recorded resolution. It conflicts with the big ball of mud, the one anti-pattern that is its outright negation. A reading list can tell you modularity is good. The record can tell you what you have already committed to by choosing it, what you will get for free, and what it refuses.

Its descriptors meet the tree. It is violated by cyclic dependencies, shared mutable state and boundary leakage. It is detected by dependency cycles and an unstable module graph, measured by a modularity score, graph density and instability, and refactored by splitting the module, introducing a boundary or inverting the dependency. It is enforced by module rules, package ownership and fitness functions, and that last slot is the difference between a canon and a book: a principle with an empty enforced-by is a wish.

The ten slots every principle carries, and the gate that proves the edges resolve.
flowchart TB
    principle["A principle · one canonical id, one kind, one category, a severity"]
    requires["requires · cannot hold without"]
    reinforces["reinforces · holds more easily beside"]
    enables["enables · makes possible"]
    tensions["in tension with · a record it pulls against, resolved by mechanism"]
    conflicts["conflicts with · the anti-patterns that negate it"]
    violated["violated by · what a violation looks like"]
    detected["detected by · the signals that see it"]
    measured["measured by · the numbers that size it"]
    refactored["refactored by · the repairs that reverse it"]
    enforced["enforced by · the gates that hold it"]
    principle --> requires
    principle --> reinforces
    principle --> enables
    principle --> tensions
    principle --> conflicts
    principle --> violated
    principle --> detected
    principle --> measured
    principle --> refactored
    principle --> enforced
    gate["A resolution gate · every edge resolves to a record or a defined term, every kind is in range, or the gate refuses"]
    requires -.-> gate
    tensions -.-> gate
    conflicts -.-> gate
The record types and the edges between them: four edges stay among principles and terms, one lands on an anti-pattern.
classDiagram
    class Principle {
        id
        name
        kind
        category
        severity
        violatedBy
        detectedBy
        measuredBy
        refactoredBy
        enforcedBy
    }
    class Term {
        id
        name
        kind
        definition
    }
    class AntiPattern {
        id
        absentControl
    }
    class Layer {
        id
        observes
        feeds
    }
    Principle --> Principle : requires
    Principle --> Principle : reinforces
    Principle --> Principle : enables
    Principle --> Term : tensions with
    Principle --> AntiPattern : conflicts with
    Principle --> Layer : member of

B1Every record has a kind

Every record carries a kind from a closed taxonomy, and the kind is decided by what the record is rather than by what its name suggests. A reading list calls everything on it a principle. A canon cannot, because the kind decides what may point at a record, how a tension with it resolves and whether a check can measure it, so a wrong kind is a wrong answer to every one of those questions at once.

B1.1Read the definition, not the name

A record's kind follows from its definition, and the taxonomy of kinds is closed. Everything on a reading list is called a principle, so a quality, a mechanism and a technique are argued as though they were rules.

A quality that a system exhibits to a degree is filed as a principle, a check is written to enforce it as a rule, and the check has nothing to return because a degree has no violation to report. Names are chosen for recognition and definitions are written for precision, so the name of a record tends to overclaim its kind, and the overclaim is only visible once someone reads the definition against the taxonomy.

Assign a kind from the definition, never from the name, and let the gate hold the two to each other. Read the definition and ask what it describes: a degree a system exhibits, a number, a rule that prescribes, a rule that must hold, a facility, a method, an arrangement, a representation, a produced thing, a convention of expression, or a condition to avoid. Assign the kind from that reading and never from the name, and hold each kind's discriminator as data so a record whose definition disagrees with its kind is refused rather than shipped.

Take any record and cover its name. Read the definition and name the kind from the definition alone. If it differs from the kind the record carries, the record is mis-filed, and every edge that points at it has been reasoning about the wrong thing.

A kind classifies what a record is, never how important it is. Two records of the same kind can differ in severity, and a quality that a whole system depends on is still a quality rather than a rule, because the taxonomy answers one question and severity answers another.

B1.2The fourteen kinds

An anti-pattern is a condition a well-designed system avoids. A metric is a measurement, a quality attribute is the property being measured. A principle prescribes, a constraint binds. A capability is what can be done, a mechanism is the facility that does it, a technique is the method a person applies, an approach is the strategy behind the method, and a pattern is the design-level arrangement.

A model is the conceptual representation, an artifact is the concrete thing produced, and a style is a convention of expression. The gate checks that every kind is in range, and for a defined term it checks that the kind agrees with the definition, so a term cannot call itself a principle while defining a metric.

B1.3What a name overclaims

Reading a kind off a name is the mistake the taxonomy exists to catch. Homoiconicity is a quality attribute, a degree a system exhibits, whatever a reading list calls it. Orchestration and choreography are mechanisms, facilities that do a thing. Pure functions are a technique a person applies. Event sourcing and CQRS are patterns, arrangements a design takes. Fail fast is a principle and idempotency is a principle, because each prescribes.

The kind decides what the record can do in the canon. A check can enforce a principle and measure a metric, but it cannot enforce a quality attribute, because a degree has no violation to report. A tension between two principles can separate by scope, while a tension between a principle and a quality attribute can only be traded. A wrong kind is a wrong answer to both questions at once.

The kind is read from the definition, and only one edge may point at an anti-pattern.
flowchart TB
    record["A record"]
    question{"What does the definition say it is?"}
    property["a property held to a degree · quality attribute"]
    number["a measurement · metric"]
    rule["a rule that prescribes · principle"]
    bound["a rule that must hold · constraint"]
    facility["a facility that does it · mechanism"]
    method["a method a person applies · technique"]
    arrangement["a design-level arrangement · pattern"]
    avoided["a condition to avoid · anti-pattern"]
    record --> question
    question --> property
    question --> number
    question --> rule
    question --> bound
    question --> facility
    question --> method
    question --> arrangement
    question --> avoided
    polarity["The polarity law · only conflicts-with may point at an anti-pattern"]
    avoided -.-> polarity
The fourteen kinds, grouped by the question each answers.
flowchart TB
    kind(("kind"))
    rule["a rule"]
    measure["a measure"]
    doing["a doing"]
    shape["a shape"]
    avoid["a condition to avoid"]
    kind --> rule & measure & doing & shape & avoid
    rule --> principle["principle · prescribes"] & constraint["constraint · must hold"]
    measure --> metric["metric · a number"] & quality["quality attribute · a degree"]
    doing --> capability["capability · what can be done"] & mechanism["mechanism · the facility"] & technique["technique · the method"] & approach["approach · the strategy"]
    shape --> pattern["pattern · an arrangement"] & model["model · a representation"] & convention["style · a convention"] & artifact["artifact · a produced thing"]
    avoid --> anti["anti-pattern"]
A kind record · the discriminator, what it is distinguished from, and the signatures a definition must carry
export interface KindRecord { readonly kind: Kind; readonly discriminator: string; readonly distinguishesFrom: string; readonly definitionSignatures: readonly string[]; } export const kindAgrees = (term: { readonly kind: Kind; readonly definition: string }, taxonomy: readonly KindRecord[]): boolean => { const record = taxonomy.find((entry) => entry.kind === term.kind); return record !== undefined && record.definitionSignatures.some((signature) => term.definition.includes(signature)); };

C1The canon is grouped twice

The canon is grouped twice, and the two groupings answer different questions. The records are held in topical categories, so a reader looking for least privilege finds it beside the other security records and backpressure beside the other resilience records. Each category is then a member of one layer, and the layers are where tensions resolve, because a layer is a scope a principle can hold whole in.

C1.1By topic for people, by layer for checks

A canon is grouped by topic for people and by layer for checks, and the layer is derived from the topic rather than stated beside it. A canon organised one way answers one question, and the other question is answered from memory.

Two principles pull against each other, the reviewer looks for the domain each belongs to, and finds two topical categories that say nothing about scope, so the tension is settled by whoever argues longer. One grouping cannot serve both a reader and a check, because a reader looks by topic and a check decides by scope, and a canon that picks one leaves the other question answered by guesswork.

Group by topic for people and by layer for checks, with one derivation between the two. Group records by topic for the reader who is looking for one, and map every topical group to exactly one layer for the check that has to decide a scope. Keep the two groupings as data with one derivation between them, so a record's layer is read from its category and never stated twice. Record which layer observes, feeds or cuts across which, because that topology is what a scope-separation between two principles is decided against.

Take any principle and name its layer without opening the record, from its category alone. If the category does not decide it, the grouping is a reading order and not a scope, and no tension that names this principle can be resolved by data.

A grouping is a lookup structure and never a claim about a record. A principle in the security category is not more important than one in the structural category, and a layer is not a rank; the layer says only which scope a principle holds whole in.

C1.2The layers

Four layers form the core. Computation is stateless and its outputs are frozen. Resource is stateful, and every handle has one owner and a bounded lifetime. Execution is control flow and events. Structural applies to everything and observes itself.

Beneath the core sit human factors, which bound the whole by what a person can hold, and evolution, which says how the whole changes over time. Around the core sit the layers that cut across it: correctness, security, performance, contracts, causality, declarative design, extensibility, observability, enforcement, the atomic boundary, domain modeling and design patterns.

The topology records which layer observes, feeds or cuts across which. A principle stated in one layer reaches the others by relation, never by being restated there.

C1.3One derivation, never two facts

A fact stated twice is a fact that drifts, and a layer written beside every record is a second grouping nobody re-checks when a category moves. So the layer is derived from the category and written nowhere else.

The same reasoning shapes how a record names its relations. Identity is the one thing written literally, and everything that points at a record does so by a name that resolves to that identity, so a leaf record depends on nothing beside it and a display name can be corrected without breaking an edge. Held that way the canon is a graph rather than a list, the whole of it is traversable, and each principle record reconciles to one vocabulary rather than the vocabulary bending to the records.

The four core layers, the two layers beneath them, and the layers that cut across them.
flowchart TB
    subgraph core["The four core layers"]
        computation["computation · stateless"]
        resource["resource · stateful"]
        execution["execution · control flow and events"]
        structural["structural · applies to all, observes itself"]
    end
    human["human factors · what a person can hold"]
    evolution["evolution · how the whole changes"]
    subgraph crosscut["Cross-cutting layers · each cuts across the structural core"]
        correctness["correctness"]
        security["security"]
        performance["performance"]
        contracts["contracts"]
        causality["causality"]
        declarative["declarative"]
        extensibility["extensibility"]
        observability["observability"]
        enforcement["enforcement"]
        atomic["atomic boundary"]
        domain["domain modeling"]
        patterns["design patterns"]
    end
    resource -- observe --> computation
    computation --> execution
    resource --> execution
    execution --> structural
    structural -- feedback --> execution
    structural --> human
    structural --> evolution
    evolution --> human
    crosscut -. cross-cuts .-> structural

D1Computation and resource

The core of the canon is a split between what happens to data and where data lives. Computation is stateless: data flows through, outputs are frozen, the same input gives the same result, and uncertainty is marked rather than hidden. A resource is stateful: it has exactly one owner, a lifetime bounded by its owner's, and a release that is guaranteed by structure rather than by discipline. The split is the first question to ask of any unit, because the two halves are answered by different rules on the same question.

D1.1What happens to data, where data lives

Computation is stateless and frozen, a resource has one owner and a bounded lifetime, and release is structural. Most code mixes the two, so data that should have been frozen is mutated and handles that should have been owned are shared, and the leaks are found in production.

A cache with no capacity grows until the process dies, a listener nobody unsubscribes fires against a component that was removed an hour ago, and both were written correctly by their authors. A stateful thing treated as stateless leaks, because nothing owns its release, and a stateless thing treated as stateful drifts, because it acquires a lifetime nothing bounds.

Split the design into stateless computation and owned resources before writing either. Sort every unit into computation or resource before designing it. Freeze what computation produces, assign each variable once, and let a computation carry no persistent state, so it can be replayed and its rollback restores meaning rather than bytes. Give every resource one owner, bound its lifetime to that owner's, pair every open with a close and every start with a stop, and release by scope or by an explicit destroy rather than by anyone remembering. Bound every cache and pool with a declared capacity and an eviction policy, and register it where it can be seen.

Take any resource and name its owner and the structure that releases it. A resource with two owners, or with a release that depends on a person, will leak, and the only question is when.

Computation and resource are answered by different rules on the same question, and the split is what stops the rules from colliding. A computation is stateless and a resource is snapshotted before mutation. Computed data is immutable and resource state is mutable but managed. A broken resource invariant halts, and computation uncertainty is marked and carried on.

D1.2The computation half

The computation half is the canon's computation core read as one rule. Immutability freezes what a computation produces. Pure functions give it no effect but its return value, and referential transparency lets any call be replaced by its result. Determinism makes the same input give the same output, which is what makes repeatability and reproducibility properties rather than hopes, and testability follows from all of them at once.

Statelessness is the same rule seen from the outside: nothing is retained between calls, so a computation can run anywhere and be replayed. Idempotency is its consequence at the edge, because a retry of a stateless step has one effect however many times it lands. Validation and verification are then cheap, since a frozen output can be compared against an expected one without a running system around it.

D1.3The resource half

Reachable is not useful. A collector frees what nothing reaches and keeps what something still points at, so an architectural leak survives collection because it is reachable: a cache entry nobody will read, an observer on a dead subject, a handle held by an injection container.

That is why every non-owning reference is weak or ephemeral, why hidden retention in injection, mapping and observer machinery is made observable and bounded, and why anything that outlives a single call carries an initialise, a run and a shutdown. Graceful shutdown is the resource rule at the scale of a process. A long-lived component with no shutdown is a leak by construction, and the leak is a hole in the contract rather than a bug in the code.

D1.4Where the halves meet

Caching is where the two halves meet most often and where the split is most often lost. A cache holds computed data, so its entries are immutable, and it is a resource, so it has one owner, a declared capacity and an eviction policy. Cache poisoning by design is what happens when the first half is forgotten, and a cache with no capacity is what happens when the second is.

Configuration externalization is the same split at the boundary of the process. What the process reads at boot is a resource with one source and a validation at the door, and what it computes from that is frozen for the run. Environment parity follows: the same computation over a different resource behaves the same, or the difference is in the resource and can be named.

Who releases a resource decides whether it leaks.
flowchart TB
    resource["A resource is acquired"]
    who{"Who releases it?"}
    discipline["A person remembering · it leaks"]
    structure["The structure · scope, a finally, an explicit destroy"]
    owner{"Exactly one owner?"}
    weak["Every other reference is weak or ephemeral"]
    shared["Shared ownership · ambiguity, then a leak"]
    resource --> who
    who -- discipline --> discipline
    who -- structure --> structure --> owner
    owner -- yes --> weak
    owner -- no --> shared
A resource's lifetime: one owner from acquisition to release, and a broken invariant halts before it releases.
stateDiagram-v2
    [*] --> Acquired : the owner opens it
    Acquired --> InUse : initialise
    InUse --> InUse : use · one owner writes
    InUse --> Released : shutdown · by scope or explicit destroy
    InUse --> Halted : invariant broken · fail fast
    Halted --> Released : the owner still releases
    Released --> [*]

E1Execution joins the halves

Execution is how the two halves meet, and it has its own rules. Children emit events and parents subscribe, never a callback passed down that calls back up. Growth is monotonic: append, never retract. Backtracking is by snapshot and reinstantiation rather than by patching state in place, ordering is by a logical sequence rather than a wall clock, and an error is part of the language rather than a sentence a consumer has to parse.

E1.1Events, growth, order, errors

Execution joins the halves through events rather than callbacks, appends rather than retracts, and treats errors as part of the language. Control flow is usually improvised per call site, so the same two halves are joined a different way in every place they meet.

A child calls back into its parent, the parent is replaced, and the child keeps calling into something that no longer exists, while the error that would have said so was a string nobody parsed. A callback couples a child to a parent it should not know, a retraction is a second path every reader must handle, a clock is a dependency on the host, and a sentence in an error is a contract nobody can dispatch on.

Join computation and resources through events, append-only growth, snapshots, ordinal time and typed errors. Let a child announce what happened as an event and let the parent decide what to do with it, so the child never holds a reference to who reacts. Append a correction as a new record that supersedes rather than editing history, and restore an earlier state by reinstantiating from a snapshot rather than by patching in place. Order by a monotonic sequence the system owns, never by the host's clock. Make every error a typed value in the system's own vocabulary, decide per error whether it halts or is carried, and let a guard that fails do so closed.

Follow one event from the child that emits it to every parent that reacts, and one error from where it is raised to where it is handled. A callback that runs upward, a retraction, or an error handled by string comparison is a place where execution has no rule.

Execution rules govern how computation and resources interact at runtime, and they do not decide what either half is. A resource that halts on a broken invariant is obeying the resource rule, and a computation that marks an uncertainty is obeying the computation rule; execution only carries the result between them.

E1.2Events and growth

Event-driven architecture is the shape the first rule produces. Domain events are what a child emits, the publish/subscribe pattern is how a parent hears them without the child knowing who listens, and an event bus is the mechanism that carries them. The observer pattern is the same relation inside one process. Execution may counter-propose by emitting an intent a parent can refuse, which is asynchronous communication with the refusal kept explicit.

Growth is monotonic: append, never retract, because a retraction is a second path every reader must handle. An append-only log is that rule as a store, and event sourcing is it as a whole architecture, where the current state is a fold over the events and a correction is a new event that supersedes. Backtracking is by snapshot and reinstantiation rather than by patching state in place, which is the memento pattern held as a rule rather than a trick.

Ordering is by a monotonic sequence or an append-only id, never a wall clock. A clock is a dependency on the host and a sequence is a dependency on nothing, which is why causality is tracked with Lamport clocks, vector clocks or hybrid logical clocks rather than timestamps, and why event ordering is a constraint the system owns. A happens-before relationship is derivable from a sequence and never from two clocks.

E1.3Errors as language

Errors are part of the language. Error handling uses the system's own vocabulary and the errors are machine-processable, so a consumer dispatches on an error rather than parsing a sentence. An inconsistent error model, one boundary raising and another returning a code, is the anti-pattern this rule refuses, and exception control flow is its twin.

A resource invariant that breaks fails fast and halts, because carrying on with a corrupt handle is worse than any crash. A computation that is uncertain marks the uncertainty and carries on, because temporary inconsistency in data is acceptable exactly while it is visible. Defensive programming is the wrong reflex here: a guard that fails open is itself a defect, since a guard exists to stop a state and one that lets the state through on error has stopped nothing. Fail safe and fail secure are the same decision made once for a domain rather than per call site.

The core split, the execution that joins it, and the structural domain that observes all three.
flowchart TB
    subgraph computation["Computation · what happens to data"]
        pure["pure functions · immutable · idempotent"]
        marked["uncertainty marked, never hidden"]
    end
    subgraph resource["Resource · where data lives"]
        owner["one owner · bounded lifetime"]
        symmetric["open then close · start then stop · guaranteed"]
        halt["invariant broken · halt"]
    end
    subgraph execution["Execution · control flow"]
        events["children emit · parents subscribe"]
        monotonic["append only · never retract"]
        snapshot["backtrack by reinstantiation"]
    end
    structural["Structural · applies to all three · observes itself"]
    resource -- observe --> computation
    computation --> execution
    resource --> execution
    execution -- feeds --> structural
    structural -- feedback --> execution
One exchange: the child emits with an ordinal, the parent accepts or refuses, and nothing calls back.
sequenceDiagram
    participant Child
    participant Bus as Event bus
    participant Parent
    Child->>Bus: emit intent · ordinal 41
    Bus->>Parent: deliver
    Parent-->>Bus: accept
    Child->>Bus: emit intent · ordinal 42
    Bus->>Parent: deliver
    Parent-->>Bus: refuse · typed error, halts: false
    Note over Child,Parent: the child never calls the parent · the log only grows

F1The structural domain

The structural domain applies to all three and observes itself, and its principles are the classical ones each paired with the practice that sharpens it. The pairing is the point: a classical principle names what to hold, and on its own it is re-argued on every review, while the practice paired with it says how the principle is recognised in a tree, which is what makes it something a check can hold.

F1.1A principle and the practice that sharpens it

A structural principle is a classical principle paired with the practice that makes it recognisable. Classical principles are agreed at a level nobody can check, so what they mean in a given file is argued every time.

Two reviewers agree that a module should have one responsibility and disagree about whether this one does, because responsibility was never derived from anything either of them could point at. A classical principle is stated at a level where everyone agrees and nobody can check, so the disagreement moves to what the principle means in this file, and that is the argument the pairing settles in advance.

Pair every classical principle with the practice that makes it recognisable in a tree. Take each classical principle and pair it with the practice that makes it recognisable. Hold one concern per module while the design is still read as one thing. Compress a repeated shape by its type rather than enumerating cases. Derive a unit's one responsibility from the invariant it protects rather than the feature it serves. Cut an interface by usage and address it by meaning. Bound complexity where a person can hold it, and leave room in an interface where the future is unknown while building nothing for a need nobody has.

Take any structural principle you hold and state, in one sentence, what a violation of it looks like in a file. If the sentence names a shape, the principle has its practice. If it names an opinion, the pairing is still missing.

A pairing sharpens a principle and never replaces it. The classical name still carries the intent a reader recognises, and the practice only says how that intent shows in a tree; a practice with no principle behind it is a house rule, and a principle with no practice is a wish.

F1.2The pairs

Separation of concerns is paired with reasoning about the whole system, so one module holds one concern while the design is still read as one thing. Simplicity is paired with compression: the simplest solution is a rule or a generator, never an enumeration, and a repeated shape is compressed by its type. A literal becomes a constant, a structure a composition, a behaviour one orchestrator, a fact one source. Do not repeat yourself is that compression named for the case of knowledge.

The single responsibility principle is derived from an invariant rather than a feature. The open/closed principle is met through few composable primitives, so extension arrives without editing what is tested. The Liskov substitution principle makes substitutable parts order-independent. The interface segregation principle cuts an interface by usage, and the dependency inversion principle addresses it by meaning rather than by location. Composition over inheritance is the practice all five share, because a composed part can be replaced and an inherited one cannot.

Code as data makes code, data and state one interchangeable structure, and homoiconicity is the degree to which a system has that property. One truth lives in versioned, queryable symbols, which is single source of truth with a location. Placement in a hierarchy reflects meaning, time is a logical sequence rather than a wall clock, and the system observes its own execution as data it can query, which is where introspection and observability meet.

F1.3Beneath the core

Human factors bound the whole by what a person can hold. Related logic stays together, which is high cohesion read as a limit on attention. Complexity stays under a declared bound, and bounded nesting depth is one such bound made checkable. Uncertainty stops the work rather than passing silently. Internals are hidden but shipped with a live inspector, so encapsulation is traded against debuggability at a point somebody chose.

Evolution says how the whole changes. Units are independent and swappable, with state protected before code. Features are added externally, through extension points, with room left deliberately unspecified. Nothing is built that nothing needs, which refuses speculative generality and premature abstraction, and what the system will not do is written down in architecture decision records rather than remembered. Evolutionary architecture with fitness functions is that domain held by checks rather than by review.

A classical principle paired with its practice, and the two domains beneath the core.
flowchart TB
    classical["A classical principle · separation of concerns, simplicity, one responsibility"]
    sharpened["The practice that sharpens it · the whole system in view, compression, an invariant"]
    pair["One pair · the principle holds, the practice says how"]
    human["Human factors · bounded by what a person can hold"]
    evolution["Evolution · state protected, room left, nothing built unneeded"]
    classical --> pair
    sharpened --> pair
    pair --> human
    pair --> evolution

G1A tension has a mechanism

Two things that pull against each other on one construct are resolved by first asking what kind of thing each of them is and which scope it holds in. An exemption is never the answer. One side winning here, for a reason nobody wrote down, is a rule with no stated scope, and it is re-litigated at every review; even an override earns the name of a resolution only once its scope and its reason are recorded. Every resolution the canon holds is readable as a record, and where no record exists the mechanism is derived from the kinds and scopes of the two sides, which is why a tension held as data is consulted before the collision happens while one held in prose is rediscovered by collision.

G1.1Recorded, never remembered

An apparent conflict has a mechanism that follows from the kinds and scopes on each side, and the resolution is recorded rather than 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, 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 whoever is reviewing.

Resolve a tension by the kinds and scopes on its two sides, and record the resolution as data. Classify each side before resolving anything: a principle, a quality, a metric, a cost, and the scope it holds in. Look for a recorded resolution first, and derive one only where none exists. Record what you derive 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. Where two enforced checks conflict on one construct, reshape the construct into the single form that expresses the real intent and satisfies every rule, and where that form is not obvious take the question to whoever owns the rules rather than answering it with an exclusion.

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; if a mechanism follows from the kinds and scopes, write it down and it is settled. 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, the second is a classification question.

G1.2The 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 nobody has thought about the case.

A recorded resolution wins outright, since a person 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 a pair nobody has decided is that it has to be measured. Mitigation is never derived: a discriminator is a judgement, and a judgement the data does not carry explicitly does not exist.

G1.3What 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 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 states the same obligation from the other direction: architecture is trade-off governance as much as principle application, and its 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, with its mechanism, its two scopes and its rule.

G1.4Two 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 expresses the real intent and satisfies every rule.

Where that form is not obvious, the question goes to the person who owns the rules rather than being answered by an exclusion. An exclusion added to make a check pass is the exemption this whole section refuses, arriving through the tooling instead of the review.

A recorded resolution wins; otherwise the kinds and scopes decide, and an exemption is none of the three.
flowchart TB
    pair["Two records pull against each other on one construct"]
    exemption["An exemption · one wins here, for a reason nobody wrote 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
Kinds against scopes: only the pair of principles in two scopes separates, and everything nobody has recorded is traded.
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]

H1Separate, trade, or mitigate

Two principles that hold in different scopes are separated by scope: on this side of the boundary the first holds whole, on that side the second holds whole, and the construct belongs to exactly one side. A pair that no boundary can split is traded. That is a principle against a quality, a metric or a cost, because a quality is a degree the whole system exhibits rather than a rule with a scope. It is also two principles that share one scope, because there is no side for either to hold whole on. In both cases the thing in tension is measured, and an operating point is chosen and written down. Two things in one scope whose difference is a matter of meaning are mitigated by a rule that names the discriminator, and that rule is always recorded, never derived.

H1.1Three mechanisms, no exemption

Scope separation, a measured trade-off and a mitigating rule are the three mechanisms, and which one applies follows from what is on each side and where it holds. One mechanism is applied to every tension, so a trade-off is argued as a violation and a semantic difference is drawn as a boundary that moves every time.

A team decides that consistency beats availability, ships a service that refuses every request under partition, and discovers that the decision was never a boundary but an operating point nobody had measured. A quality treated as a rule has no scope to be given, so a review argues a trade-off as though it were a violation, and a semantic difference treated as a scope has no boundary to draw, so a review draws one anyway and moves it next time.

Separate two principles by scope, trade a principle against a quality at a measured point, and mitigate a semantic difference with a rule. Where two principles hold in different scopes, name the scope each holds in and what holding means there, and classify the construct under review to one scope before applying either. Where a principle meets a quality, or two principles share one scope, measure the thing in tension where the construct lives, choose the operating point, and write the point down beside the choice so it is a decision with an owner rather than a mood. Where two things share a scope and differ in meaning, write the rule that names the discriminator. Where a construct genuinely sits on both sides of a scope boundary, split it along the boundary rather than weakening either rule for it.

Take any resolved tension and ask which mechanism resolved it. If both sides are principles and their scopes differ, the boundary settles it and both sides survive whole. If one side is a quality, or both sides share one scope, ask where the operating point sits and who measured it. If the pair claims a discriminator, ask where it is written.

None of the three mechanisms is an exemption. A resolution where one principle is weakened inside its own scope was not a boundary, an operating point nobody measured is a mood, a discriminator held in someone's judgement is not yet a rule, and an override that names no scope and no reason is not a resolution at all.

H1.2Separated by scope

Scope separation is the mechanism the core layers were built to produce, and its pairs recur because the computation and resource scopes answer the same question with different rules. Statelessness against snapshot before mutation: a computation flows through, a resource is snapshotted before it changes. Immutability against protecting state: computed data is frozen after creation, resource state is mutable but managed.

Fail fast against explicit invalidity: a broken resource invariant halts, a computation uncertainty is marked and carried. Not building the hypothetical against under-specifying deliberately: no code for a feature nobody asked for, and no constraint on an interface that would block one later. Each pair is the derivation at work, two principles on two layers.

The canon holds the same derivation as records where it has been consulted. Single source of truth against decentralization, and autonomy against standardization, separate because each side governs a different layer. Normalization against query performance is recorded explicitly: the canonical model is normalised and only derived read models are denormalised, never the store. The test of a boundary is that both sides survive it whole.

H1.3Traded at a measured point

A trade-off cannot be scoped away, and seeing why is what stops a review from arguing one as if it were a violation. A quality is exhibited on both sides of any boundary you draw. Consistency against availability is strong inside one transaction boundary and eventual consistency across autonomy boundaries, and both halves of that sentence are an operating point somebody chose, not a side that won. The CAP theorem is the reason the point exists and never the point itself.

Fail fast against graceful degradation shows the other road into a trade-off. Both are principles, but both hold in the one correctness scope, so there is no side for either to hold whole on. The pair is decided by where a halt costs less than a wrong answer, which is a measurement of the failure's blast radius and never a principle outranking another.

Encapsulation against debuggability is settled by hiding internals and shipping a live inspector, a point on a line rather than a wall. Backpressure against throughput is bounded producers against sustained rate. In each case the mechanism is the same: name the quality, measure it where the construct lives, choose the point, and record who chose it and against what number.

H1.4Mitigated by a rule

Mitigation is the mechanism for two things in one scope told apart by meaning. Do not repeat yourself against locality of behaviour is the recurring case. Semantics are centralised, the rules, the schemas and the one place a fact lives, and incidental co-occurrence stays local, because two passages that happen to read alike are not one fact.

No boundary separates them, since both live in the structural scope, and no measurement decides them, since neither is a quality. What resolves them is a rule that names the discriminator: is the sameness semantic or textual? A discriminator written down is consultable before the collision. One held in someone's judgement is rediscovered at every clone report, and abstracted wrongly half the time.

The recurring pairs, grouped by the mechanism that resolves them.
flowchart TB
    subgraph scope["Scope separation · both hold whole"]
        a1["stateless"] -- computation · resource --- a2["state before mutation"]
        b1["immutability"] -- computed data · resource state --- b2["state over code"]
        c1["fail fast"] -- resource halts · computation marks --- c2["explicit invalidity"]
        d1["do not build what is not needed"] -- implementation · interfaces --- d2["deliberate under-specification"]
        e1["single owner"] -- runtime resources · definitions --- e2["code as data"]
        g1["normalisation"] -- the canonical store · derived read models --- g2["query performance"]
    end
    subgraph trade["Irreducible trade-off · measured to an operating point"]
        i1["consistency"] -- inside a boundary · across autonomy boundaries --- i2["availability"]
        j1["fail fast"] -- where a halt is cheaper than a wrong answer --- j2["graceful degradation"]
        k1["encapsulation"] -- hidden internals · a live inspector --- k2["debuggability"]
        l1["backpressure"] -- bounded producers · sustained rate --- l2["throughput"]
    end
    subgraph mitigate["Mitigation · a rule names the discriminator"]
        m1["one source of truth"] -- semantic sameness · incidental likeness --- m2["locality of behaviour"]
    end