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 payments system from a few weeks ago:

module #m4k29p Payments {
aspect team: "Payments"
"Core payment processing."
aspect {
domain: "Payments"
zone: "PCI"
}
interface authorize { "Authorize a transaction" }
interface capture { "Capture an authorized amount" }
interface refund { "Issue a refund" }
}
module #x7t1qd Ledger {
aspect team: "Finance"
"Financial record keeping."
aspect {
domain: "Finance"
zone: "Internal"
}
interface record { "Record a financial event" }
}
module #b0w83r LegacyBilling {
aspect team: "Payments"
"Old billing module slated for removal."
interface charge { "Legacy charge endpoint" }
}
module #u5e7af Customer {
"End user initiating payments."
}
process #z2j6vm BasicPayment {
Customer > Payments.authorize
Payments > Ledger.record
}

Notice the #m4k29p, #x7t1qd, etc. prefixes. Those are stable IDs, minted by the formatter the first time the file was saved (see Chapter 13). They are deliberately opaque — random characters that encode no name, type, or meaning — and they never change. That is the whole point: the ID stays fixed even when the module is renamed, re-scoped, or moved, so the diff engine can recognize that “the module formerly known as Payments” is the same module after a rename. (Don’t read anything into the characters; a meaningful ID is a bug, because anything meaningful eventually goes stale and tempts you to edit it.)

A few weeks later the same files look like this:

module #m4k29p PaymentsService {
aspect team: "Payments"
"Core payment processing."
aspect {
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 #x7t1qd Ledger {
aspect team: "Finance"
"Financial record keeping."
aspect {
domain: "Finance"
zone: "Internal"
}
interface record { "Record a financial event" }
interface auditLog { "Append immutable audit entry" }
}
module #h9n42c FraudCheck {
aspect team: "Risk"
"Real-time fraud screening."
aspect {
domain: "Risk"
zone: "Internal"
}
interface screen { "Score a transaction for fraud" }
}
module #u5e7af Customer {
"End user initiating payments."
}
process #z2j6vm 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 aspects were touched. Useful for code review of the file itself, useless as a description of how the architecture changed.

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

  • Renamed: PaymentsPaymentsService (same #m4k29p).
  • Added aspect: criticality: "High" on PaymentsService.
  • Added interface: PaymentsService.void.
  • Added interface: Ledger.auditLog.
  • Added module: FraudCheck (#h9n42c).
  • Removed module: LegacyBilling (#b0w83r).
  • 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.

Payments and PaymentsService share the stable ID #m4k29p. 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 (type, 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.

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.

  • Stable IDs (#m4k29p) are opaque and permanent; they 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.

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