Services
openremap/core/services/ is the engine proper: five domains, each a package, plus
a handful of single-purpose passes. Every one of them takes bytes (or a path, at
its own boundary) and returns a dictionary — no printing, no terminal, no policy
that belongs to a UI.
Identification — services/identify/
| Module | Role |
|---|---|
identify_binary.py |
identify_binary(data, filename, container) — the entry point everything calls |
identifier.py |
identify_ecu() and the identity dict; _to_identity / _unknown_identity |
confidence.py |
score_identity() → tier + signals (ConfidenceResult, ConfidenceSignal) |
vin_scanner.py |
finds 17-character VIN candidates and scores them |
vin_report.py |
the VIN section of a report |
Identity is the foundation of every other domain: the recipe's identity_floor, the
apply ladder's Γ2 check and the health verdict all read it. That is why
identify_binary is the most-called function in the package and why its result
shape is stable.
Maps — services/maps/
| Module | Role |
|---|---|
scan_maps.py |
one-shot scan, the CLI-shaped entry point |
map_hunter.py |
the two-stage scan (axes, then tables), native-backed |
layout.py |
flash segmentation: code / calibration / erased / mixed / ident |
diff_maps.py |
match two dumps' tables by axis fingerprint and diff cells |
map_classifier.py |
probabilistic content labels (never a claim) |
map_exporter.py |
CSV rendering |
map_svg.py |
the SVG renderer behind map-image |
table_grid.py |
one table as a grid of values (map_grid method) |
xrefs.py, xrefs_v2.py |
code references see decoders |
Two rules shape this domain. The scan is structural — no family knowledge is
needed, which is how maps are found in an unsupported ECU. And a label is
probabilistic: label: duration, label_confidence: 0.64 says what the classifier
thinks, not what the table is.
Checksums — services/checksums/
| Module | Role |
|---|---|
checksum.py |
scheme detection across the known families |
ironfelix.py, nefmoto.py, ms43.py, denso.py |
one scheme per module |
report.py |
the ok / fail / skip verdict shape |
Nothing here writes a checksum. The modules verify and report; correction is
explicitly out of scope, and that is enforced by there being no such function.
health.check_checksums (public since 0.9.0) is the one-pass detector the apply
engine asks about the file it just wrote — 22–62 ms on a 4 MB dump.
The tri-state verdict is the honest part: skip means no known scheme for this
family, which is different from ok. Bosch EDC17 is the standing example — on the
real EDC17C66 pair, zero schemes verify across the whole sweep and no family
detector recognises the file, so the answer is skip, and it is never presented as
a pass.
Recipes — services/recipes/
The largest domain, and the one with the most structure. It is split by direction:
recipes/
├── recipe_builder.py stock + tuned → diff blocks
├── annotator.py flags: CODE_AREA, WEAK_ANCHOR, LOW_ENTROPY_CTX, VIN_SUSPECT
├── recipe_regions.py which flash region each change sits in
├── recipe_maps.py which tables each change touches
├── patcher.py writes a change at a resolved position
├── validate.py the shared check primitives
├── validate_strict.py ECUStrictValidator
├── validate_exists.py ECUCheckExistsValidator
├── validate_patched.py ECUPatchedValidator
├── preflight.py the pre-apply gate
├── audit.py the three-way receipt check
├── recipe_merge.py two recipes into one
├── recipe_facts.py provenance, source-first with a 4.x fallback
├── recipe_info.py read a recipe without applying it
├── volatile.py the legacy 4.5 volatile classifier (audit still reads 4.x)
├── produce/ build a schema-5.0 recipe (builder, contract, modes, report)
└── apply/ consume one (contract, policy, engine, report)
Two structural facts matter more than the file list:
-
The producers and the consumers are separate packages.
produce/writes 5.0 and verifies its own output against the apply-side reader before writing the file.apply/reads 5.0 only, and refuses anything else with ruleA2. They sharecontract.py-shaped knowledge, not code, which is why a producer bug cannot quietly loosen a consumer check. -
The apply engine composes the existing primitives.
apply/engine.pyrunsECUStrictValidator→ECUPatcher→ECUPatchedValidatorrather than reimplementing byte checks. The ladder's four rungs are fourRulesobjects —identity,exact_bytes,zero_shift,ambiguity_blocks,accounting— over one pipeline:Rung identity exact bytes ( Γ4)zero shift ( Γ7)ambiguity blocks ( Γ8)tune-strictfirmware yes yes no tune-shiftfirmware no no yes tune-uniqueany yes yes no tune-anyany no no no A new rung would be a new
Rulesobject, not a new engine.
The wizard — services/wizard/
| Module | Role |
|---|---|
plan.py |
what the measurement will do, decided up front |
fields.py |
which identity fields the dumps can decide |
signatures.py |
candidate stable byte runs, with repeated_motif() guarding against a pattern made of padding |
collisions.py |
does the candidate marker appear in any known family's fixture? |
fixtures.py |
the synthetic fixture that will be committed |
emit.py |
renders the extractor, test and report |
verify.py |
executes the generated extractor against the fixture corpus |
check.py, report.py |
the review bar and the PR description |
The wizard is the only place where code is generated, and it is the only place
where that is allowed to happen — the review bar in check.py is executed against
the rendered source before the wizard calls it verified.
The single-purpose passes
| Module | What it answers |
|---|---|
analyze.py |
the whole picture in one pass: container, identity, VIN, layout, maps, checksums, health |
health.py |
six checks, one verdict; check_checksums is the reusable detector |
coherence.py |
do identity, checksums and code references agree? agree / gap / conflict |
convert.py |
Intel HEX / S-Record → flat bytes (content sniffed) |
entropy.py |
entropy windows, used for scoring and for LOW_ENTROPY_CTX |
vin_decode.py |
VIN check-digit and WMI decoding |
scan_organize.py |
which files a scan covers, what must not be re-scanned, where a file would be filed, the copy/move itself |
scan_organize.py is worth singling out: those rules used to live in
cli/commands/scan.py, which meant the TUI could not share them. Moving them into
a service is what made the BATCH screen and openremap scan give the same answer.
Synth — core/synth/
generator.py writes a deterministic ECU-like binary: configurable size, sector
layout kind, planted maps, seed. It carries no manufacturer data, so its output
identifies as Unknown by design — which makes it safe to commit as a fixture and
useful as a demonstration.
The golden corpus in tests/corpus/ is built on it, which is why CI can run map,
layout and recipe tests without an ECU dump anywhere in the repository.