Discover the vocabulary for building a product rule (an attribute-value transformation — the feature Koongo's UI and help call "Attribute Rules" / "Rules"). GUARDRAIL: a Rule must never be keyword-matching — if a value would need MORE THAN 3 text-`contains` conditions to derive (color, material, gender… from free text), STOP and enrich per-product with set_api_source/set_ai_source instead of building the Rule. A Rule patches FEED OUTPUT only and writes NOTHING onto the products; if the user asked to enrich the products, use set_api_source/set_ai_source (or ask first) — never substitute a Rule. Call this FIRST, before you write a rule, so you use only supported operations, conditions and arguments. Returns {operations:[{name, label, group, returnType, valueType, math, attributeVariant, arguments:[{name, label, type, required, options, placeholder, optionsSource}]}], conditions:[{operator, label, group, disabledFor, arguments}], ruleSchema, examples:[{title, description, rule, sampleProduct, expectedValue}]}. A rule document has the shape {name, description, category, icon, rules:[...]}. Each rule is {sortId (int, precedence — the server reassigns it 1,2,3… by position on save), enabled (bool), type, conditions (a condition tree, optional), operationGroups:[{mode, parentMode, attributes:[...], operations:[{name, arguments:{key:value}}]}]}. A rule's `conditions` (when present) is a combine node {type:"combine", operator:"and"|"or", conditions:[ {type:"expression", attribute, operator, value} | nested combine]} — a single condition is a combine wrapping one expression, and the comparison value always goes in the expression's `value` (an operator's arguments only describe that value's input). AUTHOR the unconditional "Default Rule" FIRST, then the "Conditional Rule" exceptions — the server normalizes to that shape and reassigns sortId 1,2,3…; among conditionals a lower sortId is evaluated first and the Default Rule (at most one) is the fallback. Match a condition value's type to its attribute: a numeric attribute (dataType integer/double from list_source_attributes) needs a NUMBER not a quoted string (the server casts on save), and a text operator (disabledFor includes "number": contains/starts_with/ length_exceeds/*_any/…) on a numeric attribute is REJECTED — use greater/less/equals instead. Operations in a group run left-to-right and chain by type: an op's valueType is its INPUT and returnType its OUTPUT, so each op's returnType must match the next op's valueType. The Rules help's text "Functions" (trim, upper/lower case, strip HTML, base64…) are the single textoperations operation via its function argument; collection "Functions" (sort, unique, join, first non-empty…) are the Collections-group operations. An operation's `arguments` object keys come from that operation's argument spec here; enum arguments accept only the listed options ids. Filter to one operation group with `group`; set detail 'full' for the complete argument specs and 'compact' (default) for a lighter list. include_conditions adds the condition operators; include_examples adds curated end-to-end worked examples. VALUE / OPTIONS MAPPING (the map_attribute_value operation) remaps a source attribute's store values onto a target/channel attribute's allowed set (the CP "store value → channel value" table). Its search and replace arguments are ARRAYS OF OBJECTS {value:'<string>'} paired by index (search[i] → replace[i], SAME length) — NOT plain-string arrays. Plain strings save without error but map nothing (empty rows in the UI, passthrough at runtime) — the #1 gotcha. replace is OPTIONAL (the CP "AI Mapping (unmapped)" autofills the rest) but provide it for deterministic output. A source value NOT listed in search passes through UNCHANGED (no implicit default), so for a CLOSED target allowed-value set either map EVERY value or add a Default Rule (set_value) fallback, otherwise unmapped values violate the channel spec. Place it in an operationGroup with mode:'single', attributes:['<source code>']. SET OPTION (the setoption operation) assigns an attribute a fixed value from its CLOSED option list: put it in an operationGroup mode:'set_option', attributes:[], operations:[{name:'setoption', arguments:{value: '<option value>'}}] — value is the option VALUE, not its label. More generally, an argument whose spec has optionsSource (e.g. 'feedAttribute') means its value must be one of the target attribute's options — read them with get_attribute_options. ALWAYS validate_rule a rule before create_rule / update_rule.