The Drift Detection Engine
src/aws/drift.ts compares one InfrastructureGraph against another and reports
how they differ. Like the rules engine and the
cost estimator (src/aws/cost.ts), it is a pure, framework-free module β no
UI, no I/O β exposing a single function:
export function diffGraphs(
current: InfrastructureGraph, // the diagram on the canvas
baseline: InfrastructureGraph, // the source of truth being compared against
): DriftResult;Drift is always expressed as current relative to baseline:
- added β in
current, missing frombaseline - removed β in
baseline, missing fromcurrent - changed β matched in both, but config differs
- unchanged β matched, identical config (a count)
export interface DriftResult {
added: DriftRef[];
removed: DriftRef[];
changed: DriftChanged[]; // DriftRef + changes: { key, from, to }[]
unchanged: number;
inSync: boolean; // added + removed + changed are all empty
}Cross-source identity matching
The central problem is that resources rarely share IDs across sources: a
hand-built diagram uses internal UUIDs, a CloudFormation import uses logical IDs,
a Terraform import uses addresses, and a live export uses ARNs. Matching purely by
id would report everything as added-and-removed.
So diffGraphs matches each current resource to a baseline resource in
priority order:
- By
idβ for the rare case where both sides genuinely share one. - By a stable identity key otherwise β the ARN when present
(
arn:<arn>), elseserviceId+ lowercased name (<serviceId>::<name>).
Each baseline resource is consumed at most once (matchedBaseIds), so a match is
never double-counted. Anything in current with no match is added; any
baseline resource left unmatched is removed.
Config diffing
A matched pair is changed if any config value differs. diffConfig walks the
union of keys from both sides (sorted for stable output) and compares each by
JSON.stringify inequality, emitting { key, from, to } for every difference.
This is deep-by-serialization β nested objects/arrays compare structurally β and
intentionally simple: it has no notion of semantically-equivalent-but-different
encodings, which keeps it pure and predictable.
How results surface
The engine is wired through the flow store and rendered over the canvas:
useFlow(src/hooks/useFlow.tsx) exposes three compare entry points, all converging on a sharedrunDiff(baseline, name):compareWithFile(a file picker, accept.json,.yaml,.yml,.tf,.tofu,.tfstate,.templateβ.tofuis OpenTofu),compareWithSaved(id)(another diagram saved in this browser), andcompareWithVersion(id, label)(a version-history snapshot). JSON with aresourcesarray is a native graph; otherwise it runs throughimportAnyIaC(the same path as IaC import).runDiffcallsdiffGraphs(buildGraph(), baseline), storesdriftResult+driftBaselineName, and capturesdriftCost(current vs baseline monthly estimate).clearDriftresets it.- A
driftMarkersmemo mapsaddedandchangedrefs to canvas positions (from the layout rects), tagging each"added"or"changed". - The Canvas (
src/components/Canvas.tsx) renders an SVG overlay β using the same screen-spacefindings-overlaypattern as the validation and cost markers β dotting drifted nodes green (added) or amber (changed). - The DriftPanel (in the product page) lists counts and the added / changed /
removed entries; clicking one calls
goToResource. Removed resources have no node on the canvas, so they appear only in the panel. It also renders an Est. cost row fromdriftCostβ the baseline vs current rough monthly figure (fromsrc/aws/cost.ts) with a signed delta (β² + / βΌ β / no change), so a comparison answers βwhat does this change cost?β.
Testing
src/aws/drift.test.ts covers the engine directly: in-sync identical graphs,
added/removed detection, config-change detection, cross-source matching by
serviceId + name (a diagram UUID vs an import logical ID), and matching by
ARN when names differ. Because the engine is pure, each test is a small
in-memory fixture with no setup (see Testing).
Roadmap: exposing
diffGraphsas an MCPdiff_graphstool would let an agent compute drift headlessly against a discovered or target graph β see the roadmap.