Ranex
Cause Is Structure, Not Prose

Cause Is Structure, Not Prose

A failure sentence cannot reliably carry its category. Keep the cause as structured data, render the prose from it, and block unknown values.

7 min read
Share:X

TL;DR: A failure message should explain a cause that already exists as data, not force the next system or person to guess the cause from a sentence. The Ranex slice log keeps the evidence behind those distinctions.

You open a failed check and get one sentence: “this claim is not satisfied.” You still have to ask the question that matters. Was the work never done, was the evidence for another revision, was it forged, or did the checker refuse to admit it?

A sentence can be useful to read. It is a poor place to hide the only category that tells you what to do next.

In this note

A sentence cannot be the interface

A cause must survive as structured data because different failures demand different responses. “Absent” is not another spelling of “forged,” and neither is another spelling of “stale.”

ADR-020 records the specific Ranex failure. The kernel’s _diagnosis() already partitioned unsatisfied claims into five kinds: contradicted, failed, mismatched, stale, and absent. The admission layer added refused and unattributable. Seven events could reach a caller as the same broad outcome: a claim was not satisfied.

Then the old path discarded the distinction. _diagnosis() joined the kernel buckets into English, Evaluation exposed claim IDs without their cause, and the CLI recomputed then discarded the admission results. A later renderer had two bad choices: parse prose or invent a category it had not received.

This is not a copywriting complaint. ADR-020 ties prose parsing to a defect that reopened SLICE-002: a forgery could be reported using wording reserved for honest absence. If the words are the interface, an attacker who changes a field can influence the story the operator sees.

The cause must survive the boundary

Ranex’s decision is to compute one partition, return it as data, and render the human sentence from that same partition. The structure and the prose then have one source.

The five kernel claim causes remain at the evaluation boundary. The two admission causes do not enter Evaluation, because pure evaluate() cannot see admission rejections; the projection composes them once. Self-approval is also separate: it is an evaluation-level refusal marker, not a claim cause, and it must render even when missing_claims is empty.

That placement protects the kernel’s purity. evaluate() remains a pure function of gate, evidence, subject, and approver. It does not learn about UI wording or admission state merely because a screen needs to explain a result.

The design also preserves compatibility where it earns it. reason must remain byte-identical for existing inputs, because people read it and the journal records it. The structured field is additive, but adding it changes the evaluation record digest for new evaluations. Old journal rows keep their own digests; comparing digests across that declared boundary is not valid.

Unknown is not the nearest known cause

An unknown cause must block and render as unclassified. It must not be rounded into the closest familiar category.

ADR-020 treats the causes as unordered. There is no severity ranking that lets a renderer keep one cause and erase the others. A claim that is contradicted and missing is named once under contradiction; suite detail belongs on a failed cause, not in a new category; and a nullable admission claim_id stays null rather than being coerced into honest absence.

That discipline also gives the reader an honest response to future change. The wire accepts an unknown tag, the reader shows unclassified, and the result still blocks. The renderer does not guess. It shows the operator that the system has encountered something it cannot yet name safely.

One exit code cannot offer that honesty. A successful process can conceal skipped, missing, or unexamined work; a failed process can represent many distinct causes. A skip is not a pass follows the related move from exit codes to structured test outcomes.

What to keep structured

You do not need a governance kernel to apply this rule. Any boundary that turns a machine result into a person-facing explanation needs a stable category before it needs polished wording.

  • Define the closed causes. Name the states your consumer has to handle rather than relying on message fragments.
  • Attach the cause where it is discovered. Do not calculate it again in an API, CLI, dashboard, or report.
  • Render prose from the data. Keep human language useful without making it the protocol.
  • Make the mapping total. Test every known cause and reject a default arm that silently swallows a new one.
  • Preserve unknowns. Block, disclose, and investigate them instead of assigning the nearest familiar label.
  • Keep distinct layers distinct. A claim failure, an admission rejection, and self-approval can be related without becoming one bucket.
  • Test old wording deliberately. If compatibility matters, assert the existing sentence rather than trusting an incidental refactor.

Why ranking does not fix it

Ranking causes does not preserve them. It chooses a winner and throws information away.

ADR-020 rejects a severity rank over the seven causes. It notes Knative’s approach of ranking many causes into one and discarding what falls below the selected severity. That can be a useful presentation choice in another system, but it is the wrong data model when each cause tells an operator a different next action.

A renderer also needs exhaustive handling. ADR-020 cites typed string states that downstream code handled inconsistently because a switch’s default arm did not report an unfamiliar value. The corrective move is not to add more prose. It is to validate the closed set at deserialization so no renderer receives a known value it cannot handle.

Where Ranex stands

Ranex is pre-release, and the status must be read carefully. ADR-020 is accepted and describes the kernel decision. The README says SLICE-020 closed structured five-kind evaluation causes and self-approval, and its projection composes refused and unattributable rejections.

That does not make every interface or future board feature complete. The ADR leaves presentation, colour, glyphs, layout, and new causes out of scope. What it establishes is narrower and more useful: the cause should reach the consumer as structure, while the sentence remains a readable rendering of that structure.

Questions people actually ask

What does cause is structure, not prose mean?

ADR-020 requires Ranex to compute a per-claim cause as structured data and render reason from that same partition, rather than asking a later consumer to recover the cause by parsing English.

What failure causes does Ranex distinguish?

ADR-020 identifies five kernel claim causes—contradicted, failed, mismatched, stale, and absent—and two admission-layer causes, refused and unattributable; self-approval remains a separate evaluation-level marker.

Why is parsing a failure message unsafe?

ADR-020 forbids parsing reason because changed wording can mislabel a forgery as honest absence; the machine-readable category must survive to the renderer.

Does Ranex ship structured causes today?

The Ranex README says SLICE-020 closed structured five-kind evaluation causes and self-approval, while its projection composes refused and unattributable rejections; Ranex remains pre-release.

The next time a failure arrives as one tidy sentence, ask what structured state produced it and whether every consumer can handle that state. Try it. Break it. Tell me what broke.

Disclosure: this post was drafted with AI assistance. Every factual claim traces to the repository’s README or slice records — the same fact gate the product enforces on code. It ships only after Anthony’s own review.

About the author

Anthony Garces

Anthony Ryan M. Garces is a Senior Principal Lead Architect with 17+ years in IT, including four years at Pantheon on mission-critical platform work. He is building Ranex in public.