Implementation note. This RFC is implemented: the engine supports all four untranslatable modes (
UntranslatableModeinpackages/engine/src/types.rs), the schema carriesuntranslatables, andfeatures/untranslatables.featurecovers it.
The engine’s operation set is deliberately small: arithmetic, comparison, logical, conditional, and a handful of date operations. It only grows when real legal texts demand a new construct. This RFC is about what should happen at that moment of discovery.
Dutch law regularly uses constructs that fall outside the current operation set. The law-generate process (LLM-driven translation from legal Dutch to machine-readable YAML) has no structured way to handle this. Without an explicit “I can’t express this” path, the translator will approximate: flattening tables into fragile IF/cases trees, pre-computing dynamic values, silently omitting inexpressible conditions, or inventing creative encodings that pass validation but misrepresent the law.
We call these constructs untranslatables: the law is clear about what it means, but the engine’s formal language cannot yet express it. The term is borrowed from translation theory: the law-generate process is translation, and some things don’t cross the boundary between natural language and the engine’s operation set.
The untranslatables mechanism has three layers: generation-time detection, schema annotation, and engine runtime behavior.
The law-generate skill recognizes when a legal construct cannot be faithfully expressed with the available operations. When this happens, the translator:
untranslatables entry to the article’s machine_readable section (see layer 2)The law-reverse-validate skill detects likely workarounds that indicate the translator improvised instead of flagging:
IF with >8 cases (possible inlined bracket table)These are flagged as “possible untranslatable workaround, verify with human.”
An optional untranslatables field on the machine_readable section. Each entry has two required fields (construct, reason) and three optional fields (suggestion, legal_text_excerpt, accepted):
Untranslatables are stored as structured data, so tooling can query them. They survive across generate/validate cycles, show up in the admin dashboard and pipeline, and sit next to the article they apply to.
The accepted field (default false) indicates whether a human has reviewed and acknowledged the gap. This enables per-article scoping of runtime behavior (see layer 3).
Articles with untranslatables may still have partial execution logic for the parts that are expressible.
When the engine encounters articles with untranslatables, behavior is controlled by the --untranslatable flag. The engine always has partial execution logic (generation already excluded the untranslatable parts), so the modes control how the engine treats that known-incomplete result:
| Mode | Behavior | Use case |
|---|---|---|
error (default) | Hard error on any unaccepted untranslatable. Accepted untranslatables execute their partial logic with a trace entry. | CI, production |
propagate | Execute partial logic. Outputs from articles with untranslatables carry an UNTRANSLATABLE taint that propagates through downstream operations. | Audit, analysis |
warn | Execute partial logic, log warning in trace. No taint propagation, outputs look normal but the trace shows they’re incomplete. | Development, exploration |
ignore | Execute partial logic silently. Only valid for entries with accepted: true, unaccepted untranslatables still error. | Human-verified acceptable gaps |
Default is fail-fast. Tolerating gaps requires explicit opt-in.
The trace records untranslatables regardless of mode. The flag controls what the engine does, not whether it notices.
In propagate mode, UNTRANSLATABLE behaves like NaN in floating point: any operation involving an untranslatable input produces an untranslatable output. The result shows exactly which outputs are tainted and which are trustworthy. The trace captures the origin point.
The accepted field provides per-article scoping. In error mode (default), accepted untranslatables are allowed to execute their partial logic: a human has verified the gap is tolerable. Unaccepted untranslatables always error in error and ignore modes. This prevents a global bypass: teams cannot set --untranslatable=ignore and accidentally suppress newly-discovered gaps.
untranslatables gracefullyAlternative 1: Expand the operation set preemptively
Alternative 2: YAML comments only
# UNTRANSLATABLE: ... comments, no schema field.Alternative 3: Fail the entire law
Alternative 4: Propagate by default
--untranslatable=error for strictness.Alternative 5: Global ignore without per-article scoping
--untranslatable=ignore flag that bypasses all untranslatables.accepted field provides the needed granularity.Layer 1 requires editing the law-generate and law-reverse-validate skill definitions. No code changes.
Layer 2 requires adding untranslatables to the schema, and updating corpus, admin, and pipeline to parse, display, and track them.
Layer 3 requires adding an Untranslatable variant to the engine’s Value type, propagation logic in every operation, CLI flag parsing, trace entries, and result distinction between clean and tainted outputs.
Ordering is layer 1 → 2 → 3. Each layer is independently useful.
An exploration by Bureau Architectuur of the Dutch Ministry of the Interior into the possibilities of transparent, executable legislation.
Bureau Architectuur
Ministry of the Interior and Kingdom Relations