Proposal: Operator Taxonomy by Attribute Type in ABAC Simple Mode

Proposal: Operator Taxonomy by Attribute Type in ABAC Simple Mode

BLUF

Rename in to is any of for select fields, remove in from text fields entirely, and filter string operators (starts with, ends with, contains) to text fields only. This eliminates the in/contains semantic ambiguity, aligns select and multiselect naming patterns (is any of / has any of), and avoids the unsolved UX of multi-value free-text input. Every major platform (Jira, Salesforce, Airtable, Notion) filters operators by field type and uses distinct labels for list membership vs. substring matching.


The Problems This Solves

1. in vs contains — same word, different operations

in = exact match against a list of values ("dept is exactly Eng or Ops"). contains = substring match ("dept contains 'Eng'"). But in plain English both imply "membership." An admin who picks contains thinking "is contained in this set" gets a substring match instead — silently over-permissioning the policy.

2. in (select) vs has any of (multiselect) — same operation, different names

Both mean "match any of these values." But the select field uses in while multiselect uses has any of. An admin who learns one pattern cannot find the equivalent on the other field type. Renaming inis any of creates a parallel: is any of (select, single-value field) / has any of (multiselect, multi-value field).

3. in on text fields — no viable value input UX

For select fields, in shows a multi-chip dropdown because options are predefined. For text fields, the admin would need to type exact free-form strings into a chip input with no autocomplete — a misconfiguration magnet (typos, casing mismatches silently create non-matching rules). Half the industry (Airtable, Notion, Jira basic mode) omits multi-value exact match from text fields entirely for this reason.


Proposed Operator Matrix

Operator

Select

Text

Multiselect

CEL Output

Operator

Select

Text

Multiselect

CEL Output

is

Yes

Yes

user.attr == "value"

is not

Yes

Yes

user.attr != "value"

is any of

Yes

No

user.attr in ["v1", "v2"]

starts with

No

Yes

user.attr.startsWith("prefix")

ends with

No

Yes

user.attr.endsWith("suffix")

contains

No

Yes

user.attr.contains("substr")

has any of

Yes

"v1" in user.attr \|\| "v2" in user.attr

has all of

Yes

"v1" in user.attr && "v2" in user.attr

Why this specific set

Select fields get: is, is not, is any of

  • Values are predefined and finite — only exact-match operators apply

  • is any of replaces in with a plain-English label that parallels multiselect's has any of

  • String operators (starts with, ends with, contains) are a category error on predefined values — they create policy drift risk when new options are added

Text fields get: is, is not, starts with, ends with, contains

  • Unbounded values need pattern matching — starts with "ENG-" captures department naming conventions

  • is any of is deliberately excluded: free-form multi-value exact match has no viable simple-mode UX (no autocomplete, typo-prone). Admins who need "dept is exactly Eng or Ops" can use two is rows with global OR, or use the CEL editor

  • contains is unambiguous here — it can only mean substring match on a text field. The in/contains confusion disappears because in no longer exists on text fields

Multiselect fields get: has any of, has all of (unchanged from previous proposal)

The naming pattern

Field Type

"Match one value"

"Match any of multiple values"

Field Type

"Match one value"

"Match any of multiple values"

Select

is

is any of

Text

is

(use CEL or multiple rows)

Multiselect

has any of / has all of

The is / is any of / has any of progression is learnable: "is" for scalar exact match, "is any of" for scalar-against-list, "has any of" for set-against-set.


in vs contains — Detailed Disambiguation

These are technically different operations but the naming overlap is the root cause of confusion:

Operation

Old Name

New Name

What It Does

Example

Operation

Old Name

New Name

What It Does

Example

List membership (exact match against multiple values)

in

is any of (select only)

User's single value is one of these predefined options

Clearance is any of ["Secret", "Top Secret"]

Substring containment

contains

contains (text only)

User's text value contains this substring

Department contains "Cyber"

By restricting each operator to its appropriate field type, the ambiguity disappears entirely. is any of only appears with a dropdown value picker (predefined options). contains only appears with a text input (free-form string). The value input itself signals the operation.


Competitive Validation

Platform

Select operators

Text operators

List-match label

Substring label

Platform

Select operators

Text operators

List-match label

Substring label

Airtable

is, is not, is any of

is, is not, contains

is any of

contains

Notion

is, is not

is, is not, contains, starts with, ends with

(not offered for text)

contains

Jira

is, is not, IN

~, = (JQL only)

IN

~ (contains)

Salesforce

equals, not equal, IN

equals, contains, starts with

equals / IN

contains

Braze

equals, equals any of

equals, equals any of, contains

equals any of

contains

Segment

is, is not

is, is not, contains, starts with, ends with

(not offered for text)

contains

Key finding: No platform uses "contains" to mean list membership. "Contains" universally means substring. The platforms that serve non-technical users (Airtable, Notion) also restrict multi-value matching to select/enum fields — exactly our approach.


What This Looks Like

Select attribute (e.g., Clearance)

┌──────────────┐ ┌────────────┐ ┌────────────────────────────┐ │ Clearance ▼ │ │ is any of▼ │ │ Secret ✕ Top Secret ✕ │ └──────────────┘ └────────────┘ └────────────────────────────┘ Operator dropdown: Value input: ┌────────────┐ ┌─────────────────┐ │ is │ │ ☑ Secret │ ← predefined dropdown │ is not │ │ ☑ Top Secret │ │ is any of │ │ ☐ Confidential │ └────────────┘ └─────────────────┘

Text attribute (e.g., Department)

┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ Department ▼ │ │ starts with▼ │ │ ENG- │ └──────────────┘ └──────────────┘ └──────────────────┘ Operator dropdown: ┌──────────────┐ │ is │ │ is not │ │ starts with │ │ ends with │ │ contains │ └──────────────┘

Multiselect attribute (e.g., Programs)

┌──────────────┐ ┌────────────┐ ┌──────────────────────────┐ │ Programs ▼ │ │ has any of▼│ │ Dragon ✕ Phoenix ✕ │ └──────────────┘ └────────────┘ └──────────────────────────┘ Operator dropdown: ┌────────────┐ │ has any of │ │ has all of │ └────────────┘

Cheatsheet Update

Group the syntax cheatsheet by attribute type:

Select attributes

Operator

Meaning

CEL Example

Operator

Meaning

CEL Example

is

Exact match

user.clearance == "Top Secret"

is not

Does not match

user.clearance != "Intern"

is any of

Matches any of the selected values

user.clearance in ["Secret", "Top Secret"]

Text attributes

Operator

Meaning

CEL Example

Operator

Meaning

CEL Example

is

Exact match

user.department == "ENG-West"

is not

Does not match

user.department != "Contractor"

starts with

Begins with prefix

user.department.startsWith("ENG-")

ends with

Ends with suffix

user.department.endsWith("-CONUS")

contains

Contains substring

user.department.contains("Cyber")

Multiselect attributes

Operator

Meaning

CEL Example

Operator

Meaning

CEL Example

has any of

User has at least one of these values

"Dragon" in user.programs \|\| "Phoenix" in user.programs

has all of

User has all of these values

"Dragon" in user.programs && "Phoenix" in user.programs


Implementation

Lightweight frontend change — same pattern as PR #35896's multiselect operator filtering:

  1. Rename inis any of in operator constants (display label only; internal enum unchanged)

  2. Filter operators by attributeType: select → is, is not, is any of; text → is, is not, starts with, ends with, contains; multiselect → has any of, has all of

  3. Default operator per type: Select → is, Text → is, Multiselect → has any of

  4. Reset operator on attribute type change — switching from text (contains selected) to select resets to is

  5. Value input adapts: select operators use dropdown picker; text operators use free-form text input

No backend changes. No new UI components. No CEL generation changes.


Risk Register

Risk

Likelihood

Impact

Mitigation

Risk

Likelihood

Impact

Mitigation

Admin needs multi-value exact match on text and can't express in simple mode

Low

Medium

Use two is rows with global OR, or CEL editor. Add is any of to text in future release if demand emerges.

Renaming inis any of breaks stored policies

Low

High

Verify operator labels are display-only. If stored, implement mapping migration.

ends with proves unused, adding clutter

Low

Low

Monitor usage. Can be removed in future release without breaking policies.