feat: compound match conditions (all_of / any_of / not) (COD-481) - #19
Conversation
Upgrade handler matching from a flat all-of equality map to composable boolean condition trees. all_of/any_of take arrays of condition tables, not takes a single condition table, and forms nest arbitrarily. Leaf semantics are unchanged and the source guard stays outside the tree. Operators are recognized only by TOML shape: a scalar under an operator-named key (e.g. not = "draft") remains a legacy metadata lookup, so existing configs never change meaning. Malformed tables — empty all_of/any_of, multi-child or empty not, operators mixed with leaves, sibling operators — are rejected at config load through validate() with actionable errors. Tests cover nesting, each leaf type in compounds, back-compat flat form, legacy scalar operator-named keys, both validation rejection classes, and production-loader coverage of rite.example.toml plus every README TOML fence through load_config + validate + condition_tree.
🤖 Automated Review PanelDual-model review before opening. Reviewer A (gpt-5.6-terra) — REQUEST_CHANGES → resolved
Reviewer B (initial panel run, timed out before verdict; work verified independently)Both prior panel agents independently re-ran the full suite (47→48 tests green), traced all No unresolved blocking issues from the panel. |
Auto-Merge GateConfidence: 0.91 The implementation directly delivers COD-481: recursive Checks observed: CI
No manual-review marker or unresolved review blocker was found in COD-481 or the PR discussion. Squash-merging under the configured CodeFold gate. |
COD-481 — rite: compound match conditions (all_of / any_of / not)
Re-implemented on endver after the hosted runner's worktree (commit
538ce2fb) was lost to /tmp cleanup on builder-01. Same spec, fresh implementation.Grammar
match = { all_of = [...], any_of = [...], not = {...} }— inline tables nest arbitrarily; leaf keys (event_type,action,severity,body_contains, metadata) keep today's semantics verbatim.all_of/any_of, table fornot). A scalar under an operator-named key (not = "draft") stays a legacy metadata-lookup leaf — existing configs never change meaning.match = { event_type = "push", ... }parses to an all-of of the current leaves (back-compat pinned by tests).handler.source == event.source) stays outside the tree.Review finding fixed (gpt-5.6-terra panel)
The first cut validated compound tables only in
validate(); a library caller skipping validate could install a silently-never-matching handler. Fixed structurally: the matcher field now usesdeserialize_withso malformed compound tables are rejected at deserialization — no load path (load_configincluded) can ever produce them.validate()keeps its belt-and-braces check.Verification (endver, base
9f3d393)cargo test --all-targets: 48 passed, 0 failed (12 rite-core incl. nesting/back-compat/rejections, 12 rite-server incl. README +rite.example.tomlfences driven through realload_config, 14 docker-entrypoint, 10 rite-sources)any_of = [{ severity = "critical" }, { body_contains = "URGENT" }]matches exactly critical-or-urgent;notinsideall_ofexcludes correctlycargo clippy --all-targets -- -D warningsclean ·cargo fmt --all -- --checkclean ·rite-codegen checkexit 0 ·git diff --checkcleanCloses COD-481