The CAS trait system in garak/cas.py defines a hierarchical coding scheme with Policy, a validator, and a loader: traits follow a regex (letter-digit-letter pattern), organize into parent-child structures via get_parent_name(), and policies inherit or enforce permissions from ancestors. A Policy in garak/cas.py controls which intents or traits a probe or detector is permitted to exercise; without a policy, Garak cannot enforce content boundaries during a scan.
Trait/intent codes in garak/cas.py must match the regex ^[A-Z]([0-9]{3}([a-z]+)?)?$: a single uppercase letter, optionally followed by three digits, optionally followed by lowercase letters; invalid codes raise ValueError in get_parent_name().[1] get_parent_name() in garak/cas.py implements a three-level hierarchy: codes longer than four characters return their first four characters; four-character codes return their first character; single-character top-level codes return ''.[1]
Policy in garak/cas.py has three class-level defaults: none_inherits_parent = True (a None policy point inherits from its parent), default_trait_allowed_value = None, and permissive_root_policy = True (the root policy point is allowed by default).[1] Policy.is_permitted() in garak/cas.py recursively walks up the policy hierarchy when a point's value is None and none_inherits_parent is True; calling it on an unknown trait raises ValueError.[1] Policy.propagate_up() in garak/cas.py propagates permissiveness upward: if any child policy point is True and its parent is None, the parent is set to True. Leaf nodes (key length > 4) are processed before mid nodes (key length == 4), and top-level nodes are skipped.[1]
_validate_trait_descriptions() in garak/cas.py enforces that every trait has a non-empty name and a descr field, that all codes match the required regex, that there are no duplicate keys, and that each non-root trait has its parent present in the typology.[1] _load_trait_descriptions() in garak/cas.py returns an empty dict and logs an error if the loaded typology fails validation, so callers must handle the empty-dict case.[1] garak/data/cas/intent_detectors.json maps CAS intent labels to the detectors invoked for each intent; gaps in this file cause intents to go undetected. Engineers adding new probe intents or custom detectors must follow the file's existing schema when adding entries.
Sources