Adrian Spiridon

From Change to State: Preserving Engineering Knowledge

Note

TL;DR: Software engineering is good at preserving the current state of things—requirements, architecture, code—but often loses the reasoning behind how they got there.

My proposal is simple: for every meaningful engineering change, preserve both the change and its rationale, and the resulting current state of the artifact it affects.

  • Change history: What triggered the change, what was decided, why, and how it was implemented.

  • Current state: The consolidated, living representation of the artifact, incorporating the accepted changes.

The same principle applies to requirements, architecture, implementation, and tests. An artifact can be the result of previous changes while also becoming the starting point for subsequent ones.

AI didn’t create this need, but it makes capturing and maintaining that knowledge considerably easier.

Where the idea came from

For years, I have maintained two kinds of architecture documentation.

One describes the architecture as it exists today. The others are architecture design logs, usually organized by release, explaining what changed, why it changed, which alternatives were considered, and which decisions led to the chosen solution.

When a change is accepted, I update the living architecture documentation to reflect the resulting design, while keeping the design log as a historical record.

Recently, while reflecting on how we work with AI, I realized that this practice is not really specific to architecture.

The same pattern applies to almost every engineering artifact: requirements, specifications, architecture, implementation, and tests.

And yet, historically, we have been much better at preserving the resulting artifacts than the reasoning that shaped them.

A Jira story gets updated after refinement. An architecture diagram gets revised. Code gets committed. Tests evolve.

But the discussions, constraints, rejected alternatives, and decisions that brought those artifacts to their current state are often scattered across meetings, emails, chat messages, or simply people’s memories.

We preserve what we ended up with, but not always how or why we got there.

What if we applied the same history-and-current-state principle consistently throughout software engineering?

Two complementary views: change and current state

Consider any engineering artifact: a requirement, an architecture model, an API contract, or a software component.

There are two complementary ways of understanding it.

The current state describes what the artifact looks like at a particular point in time. It is the consolidated result of previously accepted changes.

The change history explains the significant transformations that brought it there, including their causes, constraints, decisions, and rationale.

These views answer different questions.

Someone trying to understand how a system currently works should not have to reconstruct that information from dozens of historical design documents or Jira tickets.

Conversely, someone trying to understand why a particular architectural decision was made may not find the answer in the current diagram or source code.

The current artifact tells us what exists. The history provides the context that explains why.

Neither replaces the other.

This resembles the distinction between events and state in event-driven systems or event sourcing, although the analogy is conceptual rather than literal. I am not suggesting that we should reconstruct source code by replaying documentation changes.

The principle is simply to preserve meaningful changes and their reasoning, while continuously incorporating accepted changes into the current representation of an artifact.

And importantly, that representation does not necessarily have to be another document. It may already exist as an approved requirement, an OpenAPI specification, an architecture model, source code, or automated tests.

The goal is not duplication. It is preserving knowledge that would otherwise be lost.

The same pattern applies at every engineering level

What makes this idea interesting to me is that the pattern repeats across the software development lifecycle.

Requirements

A product owner creates a Jira story.

Business users review it, the development team challenges assumptions during refinement, and previously overlooked constraints or edge cases emerge.

The story is updated until it describes the agreed requirement.

But what happened to the reasoning behind those updates?

Often, the historical record amounts to a Jira comment such as "Updated after business review."

That is the requirements equivalent of an uninformative Git commit message.

The final story tells us what was agreed, but not necessarily which alternatives were considered, why certain behaviors were excluded, or what business constraints influenced the outcome.

I would preserve the meaningful results of those discussions: clarifications, accepted or rejected proposals, important constraints, and the reasoning behind the decisions.

Separately, the accepted requirement should contribute to the consolidated product specification describing the application’s expected behavior.

We now have both the history of how a requirement was shaped and the resulting agreed behavior.

Architecture

This is where I have already been applying the approach for years.

An architecture design log captures the changes needed to support a requirement, the relevant constraints, alternatives, Architecture Decision Records (ADRs), and rationale.

The living architecture documentation is updated to describe the resulting system.

The design log explains the transformation. The current architecture explains the result.

A future engineer investigating a design decision can follow its history without forcing everyone else to read that history just to understand the current architecture.

Implementation

The same principle applies to source code.

Traditionally, a developer takes a requirement, interprets it, mentally works out an implementation strategy, writes the code, and commits the result.

Some reasoning may survive in code comments, pull-request discussions, or commit messages. Much of it disappears.

With AI-assisted development, that intermediate thinking is increasingly externalized into specifications and implementation plans.

A Jira story is transformed into a technical plan describing the intended changes, affected components, constraints, edge cases, and implementation approach.

The plan is then used to produce or guide the implementation.

This AI implementation plan is part of the engineering reasoning behind the resulting code changes. Its finalized version should be committed under changes/, alongside the implementation, while the living specification is updated to reflect the result.

And testing itself follows the same principle: significant findings and corrective decisions become part of the change history, while the resulting tests and verified behavior contribute to the current state.

Living artifacts, accepted baselines, and history

There is another subtlety.

An artifact is not necessarily historical or current by its very nature. That depends on where we are in its lifecycle.

A Jira story evolves during refinement. An architecture proposal evolves during design reviews. An implementation plan evolves as development progresses.

At some point, a particular revision is accepted.

That accepted revision becomes a baseline: an identifiable record of what was agreed at that moment.

Subsequent changes should not silently rewrite that agreement. They should be captured explicitly and linked to the previous baseline.

For example, after a requirement has been approved for implementation, a new business requirement should be recorded as an additional, traceable change rather than silently modifying the previously agreed scope.

This does not necessarily mean creating a new Jira ticket for every clarification. The important thing is preserving the accepted baseline and making subsequent changes identifiable.

Likewise, an implementation change record may evolve during development and verification.

If QA discovers a defect before the original change has been accepted, correcting the implementation and updating the same record is perfectly reasonable. It is still the same unfinished logical change.

Once accepted, the finalized record becomes stable historical knowledge. Later changes are recorded additively, with corrections or amendments made explicitly where necessary.

This gives us a recurring lifecycle:

An artifact evolves through changes, reaches an accepted state, and may subsequently become an input to further changes.

One artifact’s resulting state can become another artifact’s starting point.

It also means that the relationships between artifacts should be traceable to the relevant baselines or revisions. A specification based on an older requirement is not automatically synchronized with the latest version of that requirement.

And we should distinguish between what is expected and what is implemented. An agreed specification defines intended behavior; code and tests provide implementation and verification evidence. An actual deployed environment may be running yet another version.

Preserving these distinctions is essential if the historical records are to remain trustworthy.

Putting it into practice: spec/ and changes/

The practical implementation can be surprisingly simple. For an individual code repository, I envision the following structure:

my-repository/
├── README.md
├── src/
│
├── spec/                          # CURRENT STATE
│   ├── README.md
│   ├── architecture/
│   │   └── README.md
│   ├── capabilities/
│   │   ├── README.md
│   │   └── feature-a/
│   │       ├── README.md
│   │       └── behavior.md
│   └── interfaces/
│       └── README.md
│
└── changes/                       # CHANGE HISTORY
    ├── README.md
    ├── PROJ-101-add-feature.md
    └── PROJ-102-redesign-feature/
        ├── README.md
        ├── implementation-plan.md
        └── decisions.md

The spec/ directory is the living blueprint, describing the repository’s current capabilities, behavior, architecture, and interfaces.

The changes/ directory is the historical ledger, preserving the requirements, implementation plans (including AI-generated plans), decisions, and rationale behind individual changes.

A simple change may require just one Markdown file; a more complex change can have its own directory containing supporting documents.

I particularly like hierarchical README.md files as navigation indexes—a map of maps. They allow developers and AI agents to progressively discover relevant information without reading the entire documentation.

The workflow is straightforward: document the change, implement and verify it, update the affected living specifications, and commit the finalized change record alongside the code. Once accepted, the historical record remains stable while the living specification continues evolving.

The same structure can extend across repositories. A central architecture repository maintains system-wide specifications and change records, while individual repositories maintain their local counterparts, connected through references.

Preserving the reasoning behind the result

This is not about inventing another heavyweight documentation methodology or asking developers to write lengthy reports for trivial changes.

Many of the underlying practices already exist: Git preserves code evolution, ADRs preserve architectural decisions, issue trackers record requirements, and specification-driven workflows organize implementation.

What I am proposing is to apply the underlying principle more consistently across the entire software engineering lifecycle.

For every meaningful change, preserve the change and its rationale, while keeping the relevant living artifact aligned with the accepted result.

Whether that artifact is a requirement, an architecture model, a software specification, or source code, the principle remains the same.

We should be able to understand not only what a system looks like today, but also which decisions, constraints, and changes shaped it.

The current state tells us where we are. The change history tells us how and why we got there.

Both are valuable engineering knowledge.

And both deserve to be preserved.

<< Previous Post

|

Next Post >>