ReferenceDecision records (ADRs)
ADR 0011: The 422 answer shows only the rules on the client channel
Status: accepted
Status: accepted
Context
A runtime's helper for the answer to a denied operation (problem(), the problem-details helpers,
422 with type urn:rule-cascade:rule-violation) put the whole evaluation in the body, and the
enforcement guide told backends to return it. When a server-only rule denied, such as
org.transfer.blocked-country, its id, message and params reached the browser, although the
client manifest (specification section 7) leaves that rule out on purpose. A user could learn which
hidden checks exist and how they are configured.
Decision
- The answer keeps only the findings and effects of rules on the client channel, in order: the
rules the client manifest contains (specification section 9). Every runtime derives that list the
same way (
clientRules()): the client manifest's rule ids when there is one, otherwise the server rules withenforcementclientorboththat are not actions. - A hidden blocking finding becomes one generic finding, added after the ones shown: code
RULE-DENIED, messageThis operation is not allowed.,blocking: true, noruleand nosource. A hidden warning is dropped without one.detailcounts the blocking findings shown. The error keeps the whole result for the server's logs. - Without the client rule ids, no rule is on the client channel (fail closed): the answer holds
only
RULE-DENIEDwhen something blocked.enforcealways passes the ids; a caller that builds the answer fromevaluatepassesclientRules(). - Effects are filtered too, not only findings:
valueandstateeffects name their rule, and a server-only compute rule can expose a value. Commands need nothing, since they only exist onallow.
Consequences
- The 422 body changes for server-only denials, and Go's
ProblemDetailsgains aclientRulesargument. Released while the packages are alpha. - Existing middleware users keep their client findings unchanged: every shipped adapter builds the
body from the error, and
enforcepasses the client rules. - A hand-built answer that forgets the ids shows less, not more.
RULE-DENIED, likeRULE-EVALUATION-ERROR, is a code the runtimes produce; a ruleset finding should not use it.- A
RULE-EVALUATION-ERRORon a server-only rule becomesRULE-DENIED, so itsdetailis hidden too. The same error on a client rule still carries itsdetail. - Conformance has six new problem-details cases (2 → 8).
Alternatives considered
- Keep the shape and add an opt-in filter (
problem({ channel: 'client' })), documenting that server-only messages and params must be safe to show. No break, but the default stays leaky and safety depends on every rule author. - Keep it as it is. No work, but the error path undoes what the client manifest hides.
- Show everything when the client rule ids are absent. Friendlier to hand-built answers, but it keeps the leak as the default.