ADR-0002 — A missing field is falsy for every operator except exists¶
Status: accepted · week 1 Fills: a gap in SPEC §4.4 / ADR-002.5.
Context¶
SPEC §4.4 lists the operators but does not say what happens when the path does
not resolve. ActionProposal has several optional fields (action.method,
action.params_redacted, context.intent) and params_redacted is free-form
adapter JSON, so unresolved paths are common, not exceptional.
The question is what {field: action.method, not_equals: "GET"} does when the
proposal carries no method.
Decision¶
Resolution returns a MISSING sentinel, distinct from None (an explicit JSON
null resolves to None and is a present value).
existsis the only operator that inspectsMISSING.exists: falseis true when the field is absent or explicitly null.- Every other operator returns false on
MISSING, including the negative formsnot_equalsandnot_in. not: {...}therefore makes absence expressible:not: {field: action.method, equals: GET}is true when the method is absent.
List indexing is not supported at all. context.tags.0 resolves to MISSING.
Rules match lists with contains.
Consequences¶
- A rule's reach does not depend on which optional fields an adapter chose to populate. Two adapters that differ only in how much they fill in produce the same verdicts — which is the point of determinism.
not_equalsis not the logical negation ofequalswhen the field is absent. Both are false. This is surprising once, in the documentation, rather than surprising repeatedly in production.- Authors who want absence to count write it, and the written form reads correctly out loud.
- Reordering a list cannot change a decision, because no rule can address a list by index.
Alternatives rejected¶
MISSINGbehaves likeNone. Thennot_equals: "GET"fires on every proposal without a method, which means a rule written for HTTP actions quietly covers MCP tool calls too.- Unresolved path raises at evaluation time. The linter already rejects unknown schema paths; the remaining unresolved paths are legitimate free-form parameter lookups. Raising would mean any adapter that omits an optional key takes the kernel's decision path into an error state, and the fail-closed answer to that is to block work that should have been allowed.
- Three-valued logic (true / false / unknown). Correct, and nobody writing a YAML rule wants it.