◆ NFRGate / Rule Reference

R3 — Degradation is defined

high 🛡️ reliability · both

Any new code path with a hard (required) external dependency has an explicit failure/degradation behavior defined - fallback, circuit breaker, or a documented fail-fast - rather than an unhandled exception propagating to the caller.

Python Implementation

R3: any new code path with a hard (required) external dependency has an
explicit failure/degradation behavior defined — fallback, circuit breaker,
or a documented fail-fast — rather than an unhandled exception propagating
to the caller. rubric_store/definitions/reliability.yaml tags this `both`.

The hardest of the ten criteria added this pass to verify from syntax alone
— "is this fallback value a *real* degradation strategy" is fundamentally a
semantic question. This rule only commits to two shapes with genuinely
clear syntactic signal:

- The catch block contains a `return` statement -> an explicit value is
  handed back to the caller instead of letting the exception propagate.
  That's a real, visible degradation path -> pass.
- The catch block is empty or a bare `pass` -> the failure is silently
  absorbed with nothing defined at all -> fail (distinct from L1's
  "swallowed with no log" question; a block can log and still define no
  degradation behavior, which is exactly this case).

Everything else — a bare re-raise (could be a documented fail-fast, could
be unhandled propagation; only a human or the ticket text can tell which),
or a handler that does something else without returning or raising — is
"unclear" rather than a guess, on purpose.

Java Implementation

R3 for Java: any new code path with a hard external dependency has an
explicit degradation behavior defined, rather than an unhandled exception
propagating. Same two-clear-shapes-plus-honest-unclear design as the Python
version (degradation_defined_rule.py): a `return` statement in the catch
block is a real, visible fallback -> pass; an empty/bare-rethrow handler
around a real dependency defines nothing -> fail; anything else is
"unclear" rather than a guess.

Go Implementation

R3 for Go: any new code path with a hard external dependency has an
explicit degradation behavior defined. Not a straight port of the Python/
Java versions: Go has no exceptions to "propagate unhandled" in the first
place — every error must be explicitly returned by the function signature,
so `if err != nil { return err }` is itself Go's normal, idiomatic
fail-fast, not the failure mode R3 is really trying to catch. This rule
narrows to what's actually checkable:

- An `if err != nil` block with no statements at all -> the failure is
  discarded with nothing defined -> fail, same as L1's identical shape.
- A block whose return statement propagates the error (see
  ast_helpers.block_returns_error) -> "unclear", not a guessed pass or
  fail: this is Go's idiomatic path, but whether propagation vs. a
  fallback is the *right* call for this specific hard dependency needs
  human judgment this pass can't make.
- A block with a return statement that does NOT mention the error at all
  -> read as an explicit fallback value being returned instead of the raw
  error -> pass, at moderate confidence (this can't distinguish a real
  default value from a zero-value that happens not to say "err").
- Anything else (no return, does something else) -> unclear.

Only applies to error-check blocks whose enclosing function also makes an
outbound call somewhere (the "hard dependency" gate) — function-level
containment, not the tight per-try-block nesting Python/Java can check,
since Go's control flow is flat rather than block-scoped around a
try/except.
← All rules