Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Parity Across Languages

One engine, three languages, and the honest table of what is the same, what differs, and what is absent on purpose. This page doubles as the specification the cross-language conformance fixtures check.

Identical everywhere

BehaviourRustPythonNode
Precedence: files in order, then env, then flags/overrides✓✓✓
Formats: JSON, TOML, YAML, INI, properties✓✓✓
Env nesting __, prefix stripping, type widening✓✓✓
init fails fast; a refused reload changes nothing✓✓✓
Last-known-good cache, three modes✓✓✓
Errors carry paths, never values✓✓✓
explain/check, redacted by default✓✓✓
Telemetry series (Prometheus text)✓✓✓
Remote stores, same eight, same semantics✓✓✓
fingerprint(): the same sha256:… for the same resolved tree✓✓✓
absent as an error kind, told apart from remote✓✓✓

fingerprint() is the strongest row here, and the only one a fixture can prove across languages rather than in each. The conformance case fingerprint-agrees-across-languages pins one digest, and all three runners compute it: a Rust process, a Python process and a Node process that disagree about what configuration they are running would fail the suite rather than a dashboard.

Its two properties travel with it. Declared secrets are masked by position, so the digest moves when a secret appears or disappears and stays put when one rotates — which is what makes it safe to log, label and print in all three. And it is computed over the resolved tree rather than any rendering, so TOML on one host and YAML on another fingerprint the same.

Same idea, different idiom

ConcernRustPythonNode
Schema/validationserdedataclass / Pydantic / msgspecZod / Ajv / function
The readT::current() → Arc<T>config.current() → modelconfig.current() → object
Async waitingchanges() futurechanged_async() / events()changes() async iterator
Reload hookson_reload (watcher thread)on_reload (watcher thread, GIL held — hooks must be quick)onReload (event loop, unbounded queue)
Engine diagnosticsstderr / sink / log / tracinglogging (dynamic_config.engine), on by defaultstderr, setLogger opt-in
Group reloadReloadGroup (two-phase)ConfigGroup.reload_atomic()group.reloadAtomic()

Absent by design

WhatWhereWhy
A Wiring lifecycle objectRustmain already is one; the web crates' charter says no
Mounted health/metrics routesRust web cratesrecipes in their book — composing engine surface beats freezing choices
Request scope for WebSocketseverywherea connection is not a request; both web books state the same rule
Free-threaded wheel for the remote packagePythonabi3-only today; the base wheel has one
A compiled addon per platform matrix beyond Tier 1Nodefloors are stated in the README, extended on request
run_until(shutdown)Python, Nodeit takes a future and ends a watch when it resolves. Python's asyncio and Node's promises have their own shutdown idioms, and a translated one would be a third — release() and AbortSignal are what those languages already reach for
WatchOptionsPython, Nodethe three numbers it names — debounce, ceiling, atomic-save grace — are reachable in Rust because a Duration is one type there. Both bindings take the debounce as a number and leave the other two at the engine's defaults, which is what every caller in either language has wanted so far
RenewableSource, Revision, LeasePython, Nodetrait objects a store crate implements. A binding surfaces the documents stores produce, not the seam they implement, and a lease renewed from Python would be a lease renewed off the thread that holds it

A row moving from one table to another is a release-notes event, not an edit: the conformance fixtures live beside the engine's test suite, the bindings' CI resolves them, and a claim this page makes that a fixture cannot check is marked with (prose) — today there are none.