Skip to content

3. Reading a Diff

In most tools, “what changed in the architecture last week?” is a hard question. Diagrams aren’t comparable; PowerPoint slides have no diff. Archlang’s text source means git already knows — but raw git diff shows you removed and added lines, not architectural deltas. Renaming a module and changing nothing else looks, in a text diff, like deleting everything that mentions the old name and adding everything that mentions the new one.

Archlang’s diff is structural. It knows that a module with the same stable ID and a different name is a rename, not a delete-plus-add. It tells you so explicitly. This chapter walks through one such diff, still using only bare language.

The before state

The payments system from a few weeks ago:

module #pay001 Payments {
team: Payments
"Core payment processing."
labels {
domain: Payments
zone: PCI
}
interface Authorize { "Authorize a transaction" }
interface Capture { "Capture an authorized amount" }
interface Refund { "Issue a refund" }
}
module #led001 Ledger {
team: Finance
"Financial record keeping."
labels {
domain: Finance
zone: Internal
}
interface Record { "Record a financial event" }
}
module #leg001 LegacyBilling {
team: Payments
"Old billing module slated for removal."
interface Charge { "Legacy charge endpoint" }
}
module #act001 Customer {
"End user initiating payments."
}
process #flow01 BasicPayment {
Customer > Payments.Authorize
Payments > Ledger.Record
}

Notice the #pay001, #led001, etc. prefixes. Those are stable IDs. They were minted by the formatter the first time the file was saved (see Chapter 13). They are how the diff engine recognizes that “the module formerly known as Payments” is the same module after you rename it.

The after state

A few weeks later the same files look like this:

module #pay001 PaymentsService {
team: Payments
"Core payment processing."
labels {
domain: Payments
zone: PCI
criticality: High
}
interface Authorize { "Authorize a transaction" }
interface Capture { "Capture an authorized amount" }
interface Refund { "Issue a refund" }
interface Void { "Void an unsettled authorization" }
}
module #led001 Ledger {
team: Finance
"Financial record keeping."
labels {
domain: Finance
zone: Internal
}
interface Record { "Record a financial event" }
interface AuditLog { "Append immutable audit entry" }
}
module #frd001 FraudCheck {
team: Risk
"Real-time fraud screening."
labels {
domain: Risk
zone: Internal
}
interface Screen { "Score a transaction for fraud" }
}
module #act001 Customer {
"End user initiating payments."
}
process #flow01 BasicPayment {
Customer > PaymentsService.Authorize
PaymentsService > FraudCheck.Screen
PaymentsService > Ledger.Record
}

The text diff would tell you that Payments is gone, PaymentsService is new, a chunk of lines moved around, and several labels were touched. Useful for code review of the file itself, useless as a description of how the architecture changed.

The structural diff

Open both versions in the viewer’s diff mode (the hosted viewer accepts two directories side-by-side). You see this:

  • Renamed: PaymentsPaymentsService (same #pay001).
  • Added label: criticality: High on PaymentsService.
  • Added interface: PaymentsService.Void.
  • Added interface: Ledger.AuditLog.
  • Added module: FraudCheck (#frd001).
  • Removed module: LegacyBilling (#leg001).
  • Modified process: BasicPayment — one step inserted (PaymentsService > FraudCheck.Screen).

Each item is anchored to a node in the diagram. Renamed nodes appear in their post-rename position, marked as renamed; added nodes glow green; removed nodes glow red and dim out; modified processes highlight the inserted or removed steps.

How the rename was detected

Payments and PaymentsService share the stable ID #pay001. That’s how the diff knows. Stable IDs are anchored to modules and types, not to names. You can rename a module freely and the diff stays useful.

What about the new interface PaymentsService.Void? Interfaces don’t have stable IDs (see Chapter 13). Their identity is their dot-path inside the enclosing module. The diff engine uses heuristics on contract shape (kind, fields, description) and name similarity to detect interface renames; everything else falls back to add-plus-remove. In this example Void is genuinely new, so the diff shows it as added.

Why not give everything a stable ID? Because the cost is permanent visual noise in source files. IDs on modules buy something concrete (cross-file references survive rename). IDs on every interface inside every module would clutter source without adding much — interfaces rarely get renamed independently of their module, and when they do, the heuristic catches the common cases.

Why this matters for review

A pull-request author opens the diff view. The reviewer sees:

  • FraudCheck is new. Question: who owns it, where does it run, what data does it touch?
  • BasicPayment now routes through FraudCheck.Screen. Question: is that step blocking? What’s the SLA?
  • LegacyBilling is gone. Question: are any external systems still calling it?

Those questions are about the architecture, not about the file. The diff view surfaces them directly. The same review against a raw git diff would bury them under noise.

Summary

  • Stable IDs (#pay001) anchor module identity across renames.
  • The diff is structural: rename, add, remove, modify — not line-level.
  • Interfaces use heuristics (contract shape + name similarity), not IDs.
  • The viewer’s diff mode renders deltas directly on the diagram.
  • Chapter 14 returns to diffs in depth once we’ve covered everything they can show.

What’s next

Chapter 4: Modules → — the first primitive, in detail.