Language neutrality · October 2026

One behavior, four languages, one BKIR

The same business operation, implemented idiomatically in Java, Rust, Python and TypeScript, derives to the same set of BKIR Units. Each implementation expresses errors with a different mechanism; the derived model is identical. This page shows the operation, how each implementation expresses it, the resulting BKIR and the model limits identified.

Method

The operation is a wallet withdrawal over REST: validate the request, check that the wallet exists, that the currency matches and that there are enough funds, debit the ledger, then settle against an external gateway — and if the gateway fails, reverse the debit (a saga). It is idempotent by key. One operation, but dense: predicate and attempt conditions, a pure computation, a transactional effect, compensation, idempotency and a templated error message.

  1. A neutral contract. Behavior only — endpoint, JSON shapes, seven acceptance cases and normative rules (decimal money, validation precedence, idempotency). No BKIR vocabulary.
  2. Four idiomatic implementations, written independently and following each ecosystem's best practices, each with its own test suite covering the seven cases.
  3. Four independent derivations. A BKIR is derived from each implementation's observable behavior, without looking at the others.
  4. Comparison. Do they validate, and do they describe the same behavior?
LanguageStackError mechanismTestsDerived BKIR
JavaSpring Boot 3sealed interface + exceptions11/1111 Units, valid
RustaxumResult / enum, no exceptions16/1611 Units, valid
PythonFastAPI + Pydanticraised exceptions20/2011 Units, valid
TypeScriptNestJS + zoddiscriminated union19/1911 Units, valid

Result: convergence

4languages
11Units each, same IDs
7acceptance cases
0behavioral divergences

The four derivations describe the same observable behavior: same flow graph (read → three decisions → compute → transactional debit → settlement with compensation → outcome), same five outputs with the same codes and statuses, same predicate and attempt conditions. They differ only in source (each points to its own implementation) and in wording. Exceptions, Result values and discriminated unions — three different internal mechanisms — collapse to the same named business conditions, as BKIR's guiding principle prescribes: collapse the internal mechanism, expand the observable variation.

Comparison criteria. Unit IDs were fixed in advance so that the four derivations can be compared as a direct diff. Convergence is assessed on the modeled behavior (flow, conditions, outputs, effects), not on naming; internal naming differences between derivations reflect each implementation.

Four mechanisms, one condition COND_

How each implementation says “not enough funds”. Java returns a variant of a sealed type, Rust returns an Err, Python raises an exception and TypeScript returns a member of a discriminated union. In BKIR all four are the same thing: a condition named by its business meaning, guarding one output.

Java · Spring Boot
var w = wallet.get();
if (w.balance().compareTo(amount) < 0) {
    return new Outcome.Declined(
        money(amount.subtract(w.balance())),
        w.currency());
}
Rust · axum
if wallet.balance < req.amount {
    return Err(Failure::Insufficient {
        shortfall: req.amount - wallet.balance,
        currency: wallet.currency,
    });
}
Python · FastAPI
if balance < amount:
    raise InsufficientFunds(
        amount - balance, wallet_currency)
TypeScript · NestJS
const { balance, currency } = wallet;
if (balance.lt(req.amount)) {
  return { kind: 'declined',
    shortfall: req.amount.minus(balance),
    currency };
}

↓ all four derive to ↓

BKIR · condition + operation
kind: condition
id: COND_FONDOS_INSUFICIENTES
meaning: "el saldo de la billetera es menor que el monto a retirar"
detection: { kind: predicate, expr: "billetera.balance < amount" }
---
# inside OP_RETIRAR
flow:
  - decision: COND_FONDOS_INSUFICIENTES
    whenTrue: { to: OUT_FONDOS }
outputs:
  OUT_FONDOS:
    result: error
    code: insufficient_funds
    guard: COND_FONDOS_INSUFICIENTES

Four sagas, one edge IO_

After the debit, settlement may fail. Every implementation compensates by hand — a catch, an if let Err, an except — and maps the failure to a 502. BKIR captures it once: an attempt condition (only observable by trying) on the settlement call, and a compensate step on that error edge.

Java
try {
    gateway.liquidar(
        walletId, amount, currency, txId);
} catch (RuntimeException e) {
    ledger.reverse(walletId, amount, txId);
    return new Outcome.GatewayError();
}
Rust
if let Err(e) = state.gateway.settle(
    wallet_id, req.amount,
    &req.currency, &tx_id)
{
    state.ledger
        .reverse_debit(wallet_id, req.amount);
    return Err(e.into());
}
Python
try:
    await gateway.liquidar(
        wallet_id, amount, currency, tx_id)
except Exception:
    ledger.credit(wallet_id, amount, tx_id)
    raise GatewayError() from None
TypeScript
try {
  await this.gateway.liquidar(
    walletId, amount, currency, txId);
} catch {
  this.ledger.credit(walletId, amount);
  return { kind: 'gatewayError' };
}

↓ all four derive to ↓

BKIR · operation flow
- effect:
    transactional: true
    steps:
      - { bind: transaccionId, use: IO_LIBRO_ASENTAR, args: { walletId: walletId, monto: amount } }
- effect:
    steps:
      - use: IO_PASARELA_LIQUIDAR          # raises COND_PASARELA_CAIDA (attempt)
        args: { walletId: walletId, monto: amount, transaccionId: transaccionId }
        onCondition:
          COND_PASARELA_CAIDA:
            compensate:
              - use: IO_LIBRO_REVERTIR
                args: { walletId: walletId, monto: amount, transaccionId: transaccionId }
            to: OUT_PASARELA                 # 502, net effect zero

See it in the explorer

The four derivations are preloaded in the visual explorer. Open them one after another: the graph is the same; only the source of each Unit points to a different implementation.

Model limits identified

Each derivation records the behavior it could not express declaratively. Most of these limits appear in three or four of the languages, so they belong to the model rather than to a particular language.

Status as of October 2026. Later versions of the model may address some of these limits.
  • Provenance assumes classes. The source locator has class/method and a single origin; free functions in Rust, module functions in Python and one behavior with four implementations do not fit it well.
  • Per-field validation codes. A 422 with a list of {field, code} errors cannot be expressed declaratively from input constraints.
  • String normalization in expressions. The expression subset has no upper case/trim, so comparing a normalized currency is lossy.
  • Selective idempotency. idempotency cannot say “only successful results are replayed; errors are re-evaluated”.
  • Derived output fields. A payload field computed on the way out (the shortfall) has no declarative form.
  • Non-deterministic identifiers. A generated UUID has no place to be declared: the environment only admits a clock.
  • Implicit precedence. The order of validation checks is expressed only by the order of the flow nodes.
Error-mechanism neutrality. Result/Option values, discriminated unions and exceptions in a dynamic language map to the same Units without friction: the model does not assume exceptions.