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.

Coverage

A1An architecture is its predicate set

An architecture is only as enforced as its predicate set. Every architectural question resolves to a type, every type yields a predicate that can be computed over the tree, and every predicate does two jobs at once: it selects what to examine next and it marks when that concern is closed. An intent that never became a predicate is a habit, and a habit is held by whoever remembers it, for as long as they do.

A1.1Question, type, predicate

An architecture is its predicate set, and a predicate selects what to examine and marks when the concern is closed. Architecture is usually a set of intentions, and an intention cannot be evaluated, so nobody can say how much of the architecture is real.

A design document states twelve principles, the codebase honours four, and nobody can say which four without reading everything, because the other eight were never anything a check could evaluate. An intention has no objector, so its first violation is silent, and a system whose rules are silent is governed by attention rather than by structure.

Treat the predicate set as the architecture, and treat every intention without a predicate as absent. State every architectural intent as a question about what can drift, seen through how it drifts. Resolve the question to a type: what exists is a set, how parts are arranged is an ordering, what connects is a graph, how sure you are is a number. Derive the predicate from the type and run it over the tree as a check. Read the same predicate twice: the members it returns are the work, and its empty result is the concern closed.

List the architectural claims your system makes. Beside each, name the predicate that decides it and where that predicate runs. A claim with no predicate beside it is a sentence in a document, and the document is the only place it holds.

Determinism lives in the predicate and never in the judgement that authored it. Which cells are worth watching is a decision; whether a cell's predicate holds is a computation. Keep the decision in the person and the computation in the check, and never let a predicate encode taste.

A1.2A predicate does two jobs

The double duty is what makes derivation possible. The predicate that says an export is unreachable is the same predicate that says reachability is covered: one tells you where to work, the other tells you the dimension is watched. A project that has the first without the second accumulates checks by incident, and one that has both can derive its checks from its invariants.

Policy as code is the canon's name for the whole move, and fitness functions are the same predicates run against an architecture rather than a request. Static analysis is where most predicates live, because a shape in a tree can be decided without running anything, and design by contract is the same idea one level down: preconditions, postconditions and invariants that a check can evaluate rather than a comment can promise.

A1.3Coverage from the grid, never from a count

The coverage question is then answered from the grid rather than from a count of rules. A rule is a predicate over one cell, what can drift seen through how it drifts, and an empty cell is a drift class nothing watches whatever the rule count says.

Security theater is what a count of rules produces: the presence of controls standing in for the coverage of them. A predicate set answers the other question, which cells have something that can disagree with them, and that is the only sense in which an architecture is enforced.

From an intent to a predicate that does two jobs, and what an intent becomes without the walk.
flowchart TB
    intent["An architectural intent · a sentence about how the system should be"]
    question["A question · what can drift, seen through how it drifts"]
    type["A mathematical type · a set, an ordering, a graph, a number"]
    predicate["A predicate · computable over the tree"]
    selects["Selects what to examine next"]
    closes["Marks when the concern is closed"]
    habit["A habit · held by whoever remembers"]
    intent --> question --> type --> predicate
    predicate --> selects
    predicate --> closes
    intent -. without the walk .-> habit
What part of a design is real depends on who writes the code.
flowchart TB
    design["A design"]
    intentions["Its intentions · what the author meant"]
    predicates["Its predicate set · what a check can decide"]
    author{"Who writes the code?"}
    person["A person · intentions survive by attention, for a while"]
    model["A model · every intention with no predicate is absent"]
    real["The architecture that is real · the predicate set"]
    design --> intentions
    design --> predicates
    intentions --> author
    author -- person --> person
    author -- model --> model
    predicates --> real
    model -. only this survives .-> real

B1What can drift, seen through how it drifts

The grid has two closed axes. The dimensions are what can drift and the lenses are how it drifts, and a cell is one invariant that must hold, watched by a predicate or declared unwatched with its reason. Two closed axes are what make coverage a derivation: the cells are enumerable, so the ones nothing watches are enumerable too, and a rule set is measured against the grid rather than against the incidents that happened to produce it.

B1.1Two closed axes

Coverage is a grid of what can drift against how it drifts, and an unmeasured cell is unknown rather than clean. A rule set with no denominator reports how many checks exist, which says nothing about how many drift classes have none.

A suite reports hundreds of passing tests, the surface that fails in production was one none of them reached, and the green run was evidence over the wrong set. Without closed axes a rule set has no denominator, so coverage is reported as a count that grows with every incident and never says what is missing.

Hold coverage as a grid of two closed axes, and treat an empty cell as a drift class nothing watches. Name the dimensions along which your system can drift and the lenses through which each drift is seen, and close both lists. Place every existing check in the cell it watches, one invariant per cell. Read an empty cell as a drift class nothing watches, and read the same grid projected onto correctness as the surfaces a unit can fail in, each with its failure modes, its technique, its predicate and its evidence source. Keep unknown as a verdict distinct from pass, and never let a count of passing tests stand in for the surfaces the tests reach.

Take the checks you have and place each in its cell. Then count the empty cells. If you cannot place a check, its invariant was never stated; if you cannot count the empties, the axes were never closed.

The grid enumerates where drift can be watched and says nothing about which cells deserve a rule. A full grid is not the goal; a grid whose every cell is either watched, unwatched with a stated reason, or marked undecided is, and the third state is the one that carries the design decisions still to make.

B1.2The axes, and a cell read off them

The dimensions are what can drift: identity, composition, structure, relation, space, time, state, change, behaviour, function, cause, meaning, scale, probability, novelty. The lenses are how it drifts: structural, temporal, spatial, statistical, frequency, sequential, relational, behavioural, functional, semantic, causal, predictive, anomaly, evolutionary, fractal, transformational, invariant, optimisation, complexity.

A module reaching across a boundary is relation seen relationally, and a circular dependency is relation seen structurally. Two files claiming one role is identity seen structurally. A manifest entry rotting is composition seen through evolution. A discriminated union gaining a case nothing handles is change seen sequentially. A registry written and never read is function seen functionally. Intent living in a comment is meaning seen semantically. The same defect at every scale is scale seen fractally, and a convention followed everywhere except here is novelty seen as anomaly.

B1.3Projected onto correctness

The same grid projected onto correctness is the test-surface catalogue. Every surface a unit can fail in names its failure modes, its technique, its predicate and its evidence source, and its verdict domain carries unknown as a value distinct from pass. Property-based testing and specification-based testing are techniques a surface names, and chaos engineering is the technique for the surfaces only a running system can fail in.

An unmeasured surface is unknown rather than clean, and unknown never rounds up: a count of passing tests is evidence only over the surfaces the tests reach, and a surface nothing reaches is a claim with no evidence, however green the run. Test pyramid inversion and the mock mirage are the two ways a green run stops being evidence. Completion is the absence of required surfaces still unknown, never a percentage, because a percentage averages the surfaces that matter with the ones that cannot fail.

Two closed axes meet in a cell, and a cell is one invariant.
flowchart LR
    subgraph dimensions["What can drift"]
        identity["identity"]
        relation["relation"]
        change["change"]
        meaning["meaning"]
    end
    subgraph lenses["How it drifts"]
        structural["structurally"]
        relational["relationally"]
        sequential["sequentially"]
        semantic["semantically"]
    end
    cell["A cell · one invariant, watched, unwatched with a reason, or undecided"]
    identity --> cell
    relation --> cell
    change --> cell
    meaning --> cell
    structural --> cell
    relational --> cell
    sequential --> cell
    semantic --> cell
A corner of the grid: every cell is watched, unwatched with a reason, or undecided, and the empties are countable.
block-beta
    columns 5
    corner[" "] structural["structural"] relational["relational"] sequential["sequential"] semantic["semantic"]
    identity["identity"] i1["watched"] i2["unwatched · declared"] i3["undecided"] i4["watched"]
    relation["relation"] r1["watched"] r2["watched"] r3["undecided"] r4["unwatched · declared"]
    change["change"] c1["undecided"] c2["watched"] c3["watched"] c4["undecided"]
    meaning["meaning"] m1["unwatched · declared"] m2["undecided"] m3["watched"] m4["watched"]

C1A cell that resists an invariant

Coverage is planned by walking the grid. Enumerate the checks that exist and map each to its cell. Walk the cells, and for every empty one name the invariant that should hold there. Decide per cell whether to author the predicate or to record the cell as deliberately unwatched. The step that pays is the one where a cell resists: an invariant that cannot be stated is not a missing rule, it is an architectural intent nobody has decided, and that is the finding.

C1.1Walk the grid

A cell that resists an invariant is an undecided intent rather than a missing rule. Checks accumulate by incident, so the covered cells are the ones that already failed and the uncovered ones are the ones that will.

A team adds a check after every outage, the check count grows, and the failure that ships next lives in a cell the outages never happened to touch. A rule set built by incident has a shape decided by which incidents happened, and the drift classes that never produced an incident are exactly the ones with nothing watching them.

Derive the rule set from the grid, and read an unstatable invariant as an undecided design. Walk the grid with the checks you have, and treat every empty cell as a question rather than a gap. Where the invariant states itself, author the predicate. Where it does not, stop authoring and decide the design, because the cell is telling you that the convention it would enforce was never chosen. Record the unwatched cells with their reasons, so the unassessed set stays countable and a later reader can tell a decision from an oversight.

Find an empty cell and try to state its invariant in one sentence that could be false. If the sentence comes, you were missing a check. If it does not, you are missing a decision, and no check can be written until it is made.

A cell whose predicate nothing could ever disagree with is not authored, because a check every member satisfies for free is a field that always says the same thing and its greenness becomes evidence for the thing it never measured. Such a cell is held with the forgone property written down.

C1.2Two invariants of the walk

Two invariants keep this a method rather than a rule pile. No rule without a consuming failure mode: a rule earns its place only if a real drift class fires it, because a rule nothing can violate is ceremony, and ceremony costs the same review attention as a real rule, which is how a rule set stops being read.

And the rule set is derived while the judgement that authored it is not. Which cells need watching is a deterministic function of the architecture's declared invariants, whether an invariant was worth declaring is a decision, and the determinism stays in the predicate rather than in the deciding.

C1.3The walk in the canon

The walk itself and the gap it derives are records in the canon, and the cells the canon has not yet covered are listed rather than assumed away.

Gap analysis is the activity, and a resisting cell is its most useful output. A cell whose invariant states itself was a missing rule. A cell whose invariant will not state itself is an architecture review waiting to happen, and an architecture decision record is where its answer lands, so the next walk finds a decision rather than the same empty cell.

The coverage walk, and the branch where a cell refuses to yield a rule.
flowchart TB
    enumerate["Enumerate the checks that exist"]
    map["Map each to its cell · dimension by lens"]
    walk["Walk the grid"]
    empty{"Empty cell?"}
    name["Name the invariant that should hold there"]
    decide{"Can the invariant be stated?"}
    author["Author the predicate, or record the cell as deliberately unwatched"]
    undecided["An undecided intent · the finding is the design, never the rule"]
    enumerate --> map --> walk --> empty
    empty -- yes --> name --> decide
    decide -- yes --> author
    decide -- no --> undecided

D1The honest gaps

Every cell that earns a rule renders two ways from one entry, a detect half and a report half, and every predicate the method calls for is either running in the tree or declared absent in the one document that binds the method to the tree. The second half is the honesty of the first: a method that cannot say which of its own predicates are missing has not measured itself, and a document that describes an upgrade the tree never made is the stale description the model section names.

D1.1Detect, report, declare

A rule detects and reports from one entry, and a predicate the tree does not run is declared absent, never assumed. Checks that only block teach nothing, and methods that only describe cannot say which of their own predicates exist.

A check refuses a change with a message that names a rule id, the author works around the id, and the document that describes the method lists a predicate that has never run anywhere. A rule that only blocks teaches nothing, so the same violation returns from the next author, and a document that only describes the ideal cannot be checked against the tree, so its gaps are found by failure rather than by reading.

Render every rule as detect and report, and declare every absent predicate rather than assuming it. Author each rule as one entry with two halves: the predicate that fires on the violating shape, and the message that names the invariant and the remediation, so a failure teaches the convention rather than only blocking. Discover the entries by shape and let the gate consume the whole set, reusing whatever registry primitive the project already has for code. Then keep one document that names every predicate the method calls for and states, for each, whether the tree runs it or not, and never let an absence be assumed.

Take any finding your gate prints and ask whether it names the invariant and the remediation. Then take the document that describes your method and ask, for each predicate it calls for, whether the tree runs it. A finding that names only a rule, or a predicate nobody can locate, is the gap.

A declared gap is not a licence. Naming a predicate as absent keeps the document honest and leaves the drift class unwatched, so an absent predicate is still a cell to decide, and the declaration only says that the decision has not been made yet.

D1.2One entry, two halves

The detect half is the predicate that fires on the violating shape. The report half is the message that names the invariant and the remediation, so the failure teaches the convention rather than only blocking. Auto-remediation follows where the remediation has one correct answer, and where it does not the report still carries the handle a reasoning agent needs.

That is the registry pattern applied to enforcement: one entry per rule, discovered by shape through auto-discovery, consumed by the gate, with the whole set failing the build on drift. Where a project already has a registry primitive for code, enforcement reuses it rather than inventing a second one.

D1.3Declared absent, never assumed

The walk is also how the honest gaps are named. A predicate the method calls for and the tree does not have is declared absent, in the one document that binds the method to the tree, rather than assumed. It is normal for the epistemic and structural predicates to exist and run: whether something is reachable, consumed, grounded, drifting or covered.

The conative ones are the usual gap: a computed worth over branches, a detector for a run that stops making progress, a calibrated confidence rather than a threshold. Naming the gap is what keeps a document from describing an upgrade the tree has not made, and a method that cannot say which of its own predicates are missing has not measured itself.

A predicate the method calls for is running, declared absent, or silently assumed.
flowchart TB
    called["A predicate the method calls for"]
    exists{"Does the tree run it?"}
    runs["Declared present · it runs in the chain"]
    absent["Declared absent · named in the one document that binds method to tree"]
    assumed["Assumed · the document describes an upgrade the tree never made"]
    called --> exists
    exists -- yes --> runs
    exists -- no --> absent
    exists -. neither written down .-> assumed
A rule entry · one cell, a detect half and a report half, and a declared predicate that is running or absent
export interface Rule<Shape> { readonly cell: CellKey; readonly detect: (tree: Tree) => readonly Shape[]; readonly report: (found: Shape) => { readonly invariant: string; readonly remediation: string }; } export interface Declared { readonly predicate: string; readonly status: "runs" | "absent"; readonly because: string | null; }