RealDiff: Runtime-gedragsverschillen voor pull requests

Het instrument bouwt twee Git-revisies, observeert hun tests, leert een basislijn van 'ruis' via drie basisruns en rapporteert de eerste verandering in het gedrag binnen elke aanroepboom (call tree). Waar een broncode-diff vertelt wat er is bewerkt, vertelt RealDiff wat het effect van die bewerking is, inclusief effecten in bestanden die de pull request nooit heeft geraakt.

Waarom RealDiff gebruiken?

Een onschuldig ogende refactor kan het gedrag veranderen in een bestand dat ver verwijderd is van de bewerkte code:

public static List<(int Priority, T Value)> ByPriority<T>(
-    this IEnumerable<(int Priority, T Value)> src)
-{
-    var list = src.ToList();
-    list.Sort((a, b) => a.Priority.CompareTo(b.Priority));
-    return list;
-}
+    this IEnumerable<(int Priority, T Value)> src) =>
+        src.OrderBy(item => item.Priority).ToList();

List.Sort is niet stabiel; OrderBy is dat wel. In de meegeleverde demo veroorzaakt deze kleine wijziging in de infrastructuur een verandering in een onbewerkte pricing engine:

RealDiff: 1 gedragskloof buiten deze diff

  • DiscountEngine.SelectDiscount retourneerde "CLEARANCE40", nu "SEASONAL15".
  • CheckoutTotals.Compute retourneerde 60, nu 85.
  • 2 van de 3 tests die dit uitvoerden, reageerden niet op de verandering (geen assertie).

De bewerkte helper bevindt zich in Infrastructure.Collections; het geobserveerde effect bevindt zich in Commerce.Pricing.

Vijf minuten .NET-demo

Voorwaarden voor deze .NET-demo: Git, .NET 8 SDK en PowerShell 7.

  • Voor Java-analyse is aanvullend een JDK en de Maven/Gradle wrapper of het bijbehorende systeemhulpmiddel vereist.
  • Voor Node-analyse is Node.js en de pakketbeheerder die in het lockfile is geselecteerd vereist.
git clone https://github.com/issacnitin/RealDiff.git
cd RealDiff
dotnet build RealDiff.sln -c Release
pwsh -File tools/verify-diff.ps1 -Mutate -Change sort

De proof creëert een tijdelijke boom met de voorgestelde wijziging, past alleen SortingExtensions.cs aan, voert de basis twee keer uit plus de wijziging één keer, en schrijft de resultaten naar findings.json. Het verifieert het volgende:

  • Het bewerkte bestand draagt nul getraceerde leden bij;
  • De 'frontier' (grensvlak) is Commerce.Pricing.DiscountEngine.SelectDiscount in een onbewerkte project;
  • Twee aanroepsites zijn veranderd zonder dat een assertie reageerde;
  • Vijf uite divergerende sleutels worden teruggebracht naar drie frontier-nodes;
  • De selectie bij gelijke prioriteit is deterministisch over verschillende nieuwe processen.

Om alle onderhouden demo-modi uit te voeren:

pwsh -File tools/verify-demo-fixtures.ps1

Dit omvat sort-stabiliteit, retry-beleid en configuratie-parsing.

Architectuur

Het publieke executable is een dunne Rust-launcher die verantwoordelijk is voor argument-routing, het laden van repository-configuraties en detectie. Deze start een naastgelegen, self-contained managed component voor referentie-resolutie, builds, caching, instrumentatie en posting.

De architectuur bestaat uit één taal-neutraal trace-contract, één tracer per runtime, en een single-pass streaming Rust-engine voor diffing, frontier-detectie en resultaten:

Workflow: Rust argv, config, detectManaged orchestrationTracersrealdiff.trace/1Rust matching, noise, frontier, and findingsfindings.jsonGitHub, Azure DevOps, MCP.

De tracers per taal zijn:

  • .NET: Cecil
  • Java: javaagent + ASM
  • Node: CJS + ESM + Babel
  • Go: stable AST rewrite
  • Rust: stable syn rewrite cache
  • Python: PEP 669 sys.monitoring

TRACE-FORMAT.md is het contract tussen de tracers en de engine. De ondersteunde talen volgen dezelfde conformiteitsregels: identieke methodensets, per-sleutel event-counts en entry-ordinals, source tripwires, digest-bewijzen en nul engine-divergenties bij niet-lege runs.

Status: Early preview. De unified CLI detecteert .NET, Maven/Gradle Java, npm/pnpm/Yarn/Bun Node, Go modules, Cargo Rust en Python 3.12+ repositories op basis van conventionele root-markers.

Ondersteunde talen

TaalInstrumentatieTest/bron-integratieHuidige beperkingen
.NET 8Mono.Cecil build-time IL weavingxUnit en portable PDBsProperties, events en operators zijn uitgesloten. Type-initialiseerders zijn structureel niet observeerbaar vanwege CLR-locks (deadlock risico).
Javajava.lang.instrument agent met ASMMaven/Gradle, JUnit/TestNG, afgeleide of geconfigureerde source rootsDynamic source-set configuratie vereist source_roots. Class-initialiseerders zijn niet observeerbaar vanwege JVM-locks.
Node / TypeScriptCommonJS require hook en ESM loader met Babelnpm, pnpm, Yarn, Bun, TypeScript source maps, Jest/Vitest adaptersExact één ondersteund lockfile is vereist; workers vallen buiten scope; generators worden overgeslagen.
GoStable module-aware AST rewriting in build cachego test, originele .go parser positiesDynamische interface/functie-grenzen en niet-herschreven goroutine-grenzen worden overgeslagen.
RustStable syn/quote rewriting in SHA-256 build cachecargo test, structurele #[test] roots, originele .rs parser positiesMacro-expansies, extern/const callables en trait objects zijn onbereikbaar omdat source rewriting niet in compiler-owned code kan injecteren.
Python 3.12+PEP 669 sys.monitoring bij processtartpytest en unittest; cofilename/cofirstlineno; source AST inventoryNative/C callables zijn niet observeerbaar. Python 3.11 en ouder worden geweigerd (geen sys.settrace fallback).

Specifieke opmerkingen over Python

Python verschilt van gecompileerde talen omdat er geen build-stap is voor injectie. RealDiff voegt sitecustomize.py toe aan PYTHONPATH en koppelt sys.monitoring voordat doel-imports worden geladen.

Waarde-ondersteuning in Python:

  • Exact: None, booleans, integers, floats (incl. NaN en -0.0), strings, bytes, en built-in list, tuple, dict, set, frozenset; volledige dict state.
  • Partieel: Properties, slots, getattr, container-subklassen, diepte/breedte limieten. Ongelezen regio's markeren als <skipped>, <error>, <depth> of <truncated>.
  • Niet ondersteund: Native/C callables zonder Python code objects, dynamische/synthetische code zonder repository-bron.

Cross-language release demo's

De v0.4.0 release is getest met zes publieke sort-stabiliteit pull requests. In elk scenario:

  • De bewerkte file draagt nul getraceerde leden bij.
  • De frontier bevindt zich in onbewerkte pricing code.
  • Ten minste één call site is ongetest.
TaalMatched keysFrontier collapsePull requestHosted workflow
.NET3199 naar 3 (3.0x)realdiff-sort-dotnet#1workflow
Node129117 naar 3 (39.0x)realdiff-sort-node#1workflow
Java132117 naar 3 (39.0x)realdiff-sort-java#1workflow
Go3159 naar 3 (3.0x)realdiff-sort-go#1workflow
Rust3129 naar 3 (3.0x)realdiff-sort-rust#1workflow
Python3106 naar 3 (2.0x)realdiff-sort-python#1workflow

Installatie van de CLI

Via de all-language container

De Linux-image bevat de CLI, de Rust-engine en alle benodigde SDK's/tracers. De host heeft alleen Docker nodig:

docker pull ghcr.io/issacnitin/realdiff:v0.4.0
docker run --rm \
--volume "$PWD:/workspace" \
ghcr.io/issacnitin/realdiff:v0.4.0 \
/workspace --base origin/main --pr HEAD \
--findings /workspace/.realdiff/artifacts/findings.json

Via GitHub release

Download het archief voor jouw platform (linux, darwin, win) van de v0.4.0 release.

Linux voorbeeld:

sha256sum --check SHA256SUMS --ignore-missing
tar -xzf realdiff-v0.4.0-linux-x64.tar.gz -C "$HOME/.local/lib/realdiff"
ln -s "$HOME/.local/lib/realdiff/realdiff" "$HOME/.local/bin/realdiff"
realdiff --help

Windows voorbeeld:

Get-FileHash .\realdiff-v0.4.0-win-x64.zip -Algorithm SHA256
Expand-Archive .\realdiff-v0.4.0-win-x64.zip "$env:LOCALAPPDATA\RealDiff"
& "$env:LOCALAPPDATA\RealDiff\realdiff.exe" --help

Bouwen vanuit bron

git clone https://github.com/issacnitin/RealDiff.git
cd RealDiff
pwsh -File tools/package-cli.ps1
dotnet tool install --global RealDiff.Tool --add-source ./artifacts/packages
realdiff --help

Een analyse uitvoeren

RealDiff heeft een repository-pad en twee Git-referenties nodig.

realdiff C:\src\my-service `
--base origin/main `
--pr HEAD `
--findings C:\temp\realdiff\findings.json

Nuttige opties

  • --work <directory>: Overschrijf de tijdelijke werkmap.
  • --findings <file>: Schrijf machine-leesbare resultaten.
  • --cache-dir <directory>: Overschrijf de lokale base-trace cache map.
  • --cache-retention <n>: Laat gecachte traces verlopen na een bepaalde tijd (bijv. 12h of 7d).
  • --keep-traces <n>: Bewaar werk-traces voor een specifieke periode.
  • --ci=github / --ci=azuredevops: Resolve refs via CI-events/variabelen.

Exit-codes

CodeBetekenis
0Analyse voltooid; geen onverwachte gedragsveranderingen.
1Analyse voltooid; er zijn gedragsbevindingen.
3Analyse geweigerd omdat het bewijs onvoldoende is voor een oordeel.
4RealDiff kon de repository niet instrumenteren.
5De ongewijzigde repository kon niet gebouwd worden in deze omgeving.

Taal-specifieke vereisten

  • .NET: .NET 8 SDK. Repository moet een SDK-style solution/project bevatten en xUnit tests met Microsoft.NET.Test.Sdk.
  • Java: JDK en Maven/Gradle. RealDiff opent de vereiste java.util JPMS-boundary.
  • Node/TypeScript: Node.js, een test-script, en exact één lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock, of bun.lock). TypeScript moet bruikbare source maps emitteren.
  • Go: Stable Go en standaard go test. De CLI herschrijft broncode alleen in een externe cache; de checkout wordt niet gewijzigd.
  • Rust: Stable Rust/Cargo en #[test] tests. Gebruikt een externe content-addressed cache voor herschreven code.
  • Python: Python 3.12 of nieuwer met sys.monitoring. Gebruikt de geconfigureerde test-runner van de repository.

Repository-configuratie

Voeg .realdiff/config.yml toe voor custom instellingen:

language: node
workdir: services/api
build: npm ci && npm run build
test: npm test
test_projects:
- tests/Api.Tests/Api.Tests.csproj
source_roots:
- services/api/code/main
- services/api/code/test
include_namespaces:
- src
exclude_namespaces:
- src/generated
redaction:
  names:
  - customer_password
  types:
  - SecretEnvelope
  paths:
  - generated
baseline:
  schema: realdiff.baseline/2
  acknowledgements: []
  ignorePaths: []
  ignoreMembers: []

Base trace cache

RealDiff cached de drie gevalideerde noise-baseline traces wanneer --cache-dir is opgegeven. De cache-sleutel bevat de target SHA, taal, tracer-fingerprint en scope/redactie-configuratie.

Performance impact: Bij een testcase met 99.000 events per run:

  • Koud (vier runs): 339.705 seconden.
  • Warm (cache-hit): 53.962 seconden.
  • Reductie: 84.1% (6.3x sneller).

Om een target branch te 'warmen' zonder PR-vergelijking:

realdiff warm C:\src\my-service --target origin/main --cache-dir C:\ci-cache\realdiff

Suppression baseline

RealDiff past automatisch .realdiff/baseline.yml toe. Dit is een beleidsprojectie waarbij bekende veranderingen worden gemarkeerd als 'suppressed'.

Om acknowledgements te schrijven voor huidige actionable members:

realdiff baseline write --findings .realdiff/artifacts/findings.json

Voorbeeld van baseline.yml:

schema: realdiff.baseline/2
acknowledgements:
- id: accepted-pricing-change
  member: Commerce.Pricing.DiscountEngine.SelectDiscount(System.Decimal)
  path: src/Commerce.Pricing/DiscountEngine.cs
  baseDigest: 'sha256:4ce90f...'
  prDigest: 'sha256:809af1...'
  reason: Approved pricing migration
  expires: 2026-09-30
ignorePaths:
- id: generated-sources
  pattern: '**/generated/**'
  reason: Generated files are reviewed through their source templates

Trace-beveiliging en Threat Model

Trace-events bevatten method-identiteiten, bronlocaties en gecanonicaliseerde argument- en return-waarden. Dit kan gevoelige data bevatten.

  • Redactie: Staat standaard aan. Namen die matchen met password, token, secret, key, ssn, email, auth of credential worden als <redacted> weergegeven.
  • Integriteit: Redactie beïnvloedt de vergelijking niet. SHA-256 digests worden berekend voordat de weergave wordt geredigeerd. Twee verschillende geheimen produceren dus nog steeds een divergentie, zelfs als beide <redacted> tonen.
  • Opslag: Werk-traces worden standaard na analyse verwijderd.

Resultaten lezen

findings.json is het primaire resultaat.

Kernconcepten:

  • Expected: Gedrag is veranderd in een bestand dat in de diff staat.
  • Unexpected: Gedrag is veranderd in een bestand buiten de broncode-diff.
  • Behavior gap: Ten minste één uitvoerende test reageerde niet op de gedragsverandering.
  • Test-covered change: Elke uitvoerende test reageerde; dit wordt als bewijs genoteerd, niet als fout.
  • Frontier: Het laagste gewijzigde lid waarvan de descendanten ongewijzigd zijn. Hogere callers worden als collateral beschouwd en onderdrukt.
  • Coverage: Elk bewerkt bestand rapporteert getraceerde leden, call sites en calls. "Zero" betekent niet-geobserveerd, niet "ongewijzigd".

CI Integratie

GitHub Actions

Gebruik de Docker Action na een full-history checkout:

permissions:
  contents: read
  pull-requests: write
steps:
- uses: actions/checkout@v4
  with:
    fetch-depth: 0
- uses: issacnitin/RealDiff@v0.4.0
  env:
    GITHUB_TOKEN: ${{ github.token }}

Azure Pipelines

Gebruikt de all-language image in een container job. Voeg het toe als een Build validation branch policy.

realdiff <repo> --ci=azuredevops --findings findings.json
realdiff post --provider=azuredevops --findings findings.json

Optionele AI-uitleg

Bepaalde resultaten kunnen worden aangevuld met een uitleg via een LLM (bijv. via ANTHROPICAPIKEY), beperkt tot exacte observaties en diff-citaten. Dit verandert het deterministische resultaat niet.

Hoe het werkt (Stappenplan)

  1. Resolve base en proposed refs.
  2. Creëer geïsoleerde worktrees.
  3. Bouw en instrumenteer de code.
  4. Voer de basis drie keer uit.
  5. Voer de voorgestelde wijziging één keer uit.
  6. Leer nondeterministische sleutels.
  7. Vergelijk aanroepen en waarden.
  8. Collapse naar de 'behavior frontier'.
  9. Attribueer bewerkt vs. onbewerkt.
  10. Genereer findings.json en PR-comments.

Eerlijke beperkingen

RealDiff analyseert uitgevoerd gedrag, niet alle mogelijke scenario's.

  • Niet-uitgevoerde methoden hebben geen runtime-bewijs.
  • .NET type-initialiseerders, properties, events en operators worden overgeslagen.
  • Java static initialiseerders en Node generators worden als 'skipped' gemarkeerd.
  • Node worker threads vallen buiten scope in versie 1.
  • Rust opaque/generic/trait-object regio's zijn onbereikbaar.
  • Drie basisruns samplen nondeterminisme, maar karakteriseren niet elk mogelijk scenario.
  • Target tests worden uitgevoerd met de rechten van de CI agent; RealDiff is geen sandbox.

Repository-indeling

PadDoel
src/RealDiff.Launcher.RustPublieke launcher en managed-process boundary.
src/RealDiff.Engine.RustStreaming Rust diff, frontier en findings engine.
src/RealDiff.CliOrkestratie van builds, caches en instrumentatie.
src/RealDiff.Tracer.NET runtime hooks en value rendering.
src/RealDiff.Java.AgentJava agent en ASM rewriting.
src/RealDiff.NodeNode/Babel hooks en test adapters.
src/RealDiff.GoGo source rewriter en runtime.
src/RealDiff.Rust.TracerRust rewrite cache en runtime.
src/RealDiff.ContractsTrace en manifest wire formats.
src/RealDiff.McpOptionele MCP server.
tools/WeaverMono.Cecil build-time instrumentatie.
samples/ & src/Commerce.PricingExecutable behavior-diff fixtures.
tools/verify-*.ps1End-to-end executable proofs.