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.SelectDiscountretourneerde "CLEARANCE40", nu "SEASONAL15".CheckoutTotals.Computeretourneerde 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.SelectDiscountin 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, detect → Managed orchestration → Tracers → realdiff.trace/1 → Rust matching, noise, frontier, and findings → findings.json → GitHub, 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
| Taal | Instrumentatie | Test/bron-integratie | Huidige beperkingen |
|---|---|---|---|
| .NET 8 | Mono.Cecil build-time IL weaving | xUnit en portable PDBs | Properties, events en operators zijn uitgesloten. Type-initialiseerders zijn structureel niet observeerbaar vanwege CLR-locks (deadlock risico). |
| Java | java.lang.instrument agent met ASM | Maven/Gradle, JUnit/TestNG, afgeleide of geconfigureerde source roots | Dynamic source-set configuratie vereist source_roots. Class-initialiseerders zijn niet observeerbaar vanwege JVM-locks. |
| Node / TypeScript | CommonJS require hook en ESM loader met Babel | npm, pnpm, Yarn, Bun, TypeScript source maps, Jest/Vitest adapters | Exact één ondersteund lockfile is vereist; workers vallen buiten scope; generators worden overgeslagen. |
| Go | Stable module-aware AST rewriting in build cache | go test, originele .go parser posities | Dynamische interface/functie-grenzen en niet-herschreven goroutine-grenzen worden overgeslagen. |
| Rust | Stable syn/quote rewriting in SHA-256 build cache | cargo test, structurele #[test] roots, originele .rs parser posities | Macro-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 processtart | pytest en unittest; cofilename/cofirstlineno; source AST inventory | Native/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
dictstate. - 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.
| Taal | Matched keys | Frontier collapse | Pull request | Hosted workflow |
|---|---|---|---|---|
| .NET | 319 | 9 naar 3 (3.0x) | realdiff-sort-dotnet#1 | workflow |
| Node | 129 | 117 naar 3 (39.0x) | realdiff-sort-node#1 | workflow |
| Java | 132 | 117 naar 3 (39.0x) | realdiff-sort-java#1 | workflow |
| Go | 315 | 9 naar 3 (3.0x) | realdiff-sort-go#1 | workflow |
| Rust | 312 | 9 naar 3 (3.0x) | realdiff-sort-rust#1 | workflow |
| Python | 310 | 6 naar 3 (2.0x) | realdiff-sort-python#1 | workflow |
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
| Code | Betekenis |
|---|---|
| 0 | Analyse voltooid; geen onverwachte gedragsveranderingen. |
| 1 | Analyse voltooid; er zijn gedragsbevindingen. |
| 3 | Analyse geweigerd omdat het bewijs onvoldoende is voor een oordeel. |
| 4 | RealDiff kon de repository niet instrumenteren. |
| 5 | De 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.utilJPMS-boundary. - Node/TypeScript: Node.js, een test-script, en exact één lockfile (
package-lock.json,pnpm-lock.yaml,yarn.lock, ofbun.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,authofcredentialworden 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)
- Resolve base en proposed refs.
- Creëer geïsoleerde worktrees.
- Bouw en instrumenteer de code.
- Voer de basis drie keer uit.
- Voer de voorgestelde wijziging één keer uit.
- Leer nondeterministische sleutels.
- Vergelijk aanroepen en waarden.
- Collapse naar de 'behavior frontier'.
- Attribueer bewerkt vs. onbewerkt.
- Genereer
findings.jsonen 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
| Pad | Doel |
|---|---|
src/RealDiff.Launcher.Rust | Publieke launcher en managed-process boundary. |
src/RealDiff.Engine.Rust | Streaming Rust diff, frontier en findings engine. |
src/RealDiff.Cli | Orkestratie van builds, caches en instrumentatie. |
src/RealDiff.Tracer | .NET runtime hooks en value rendering. |
src/RealDiff.Java.Agent | Java agent en ASM rewriting. |
src/RealDiff.Node | Node/Babel hooks en test adapters. |
src/RealDiff.Go | Go source rewriter en runtime. |
src/RealDiff.Rust.Tracer | Rust rewrite cache en runtime. |
src/RealDiff.Contracts | Trace en manifest wire formats. |
src/RealDiff.Mcp | Optionele MCP server. |
tools/Weaver | Mono.Cecil build-time instrumentatie. |
samples/ & src/Commerce.Pricing | Executable behavior-diff fixtures. |
tools/verify-*.ps1 | End-to-end executable proofs. |
Groetjes,