← all work

Condition Builder

Lumos · 2025

Timeframe: Discovery to GA, 3 months

Lumos is an identity and access platform. Almost every product in it eventually needs to say the same thing: this rule for these people. Access policies say it, approval rules say it, lifecycle management workflows say it - you get it. Over time, each one had been built when it was needed, by whichever team needed it.

TL;DR

Our existing product areas had each developed their own way to describe a set of users. They worked individually, but users needed to constantly learn new patterns and hit different limitations. There's a generic need nobody had named yet: admins want to control how the product behaves for different sets of people, everywhere, and that need kept arriving in new surface areas.

I built a conditional-logic primitive, adopted across three areas, and added an evaluation mode so an admin can test a rule against real data and see exactly who it matches before applying it.

Challenges & Opportunities

Building a bespoke experience per area was compounding costs in different aspects:

  • Inconsistent capability - Conditional logic and expression-language support differed per area, because each team had implemented its own
  • Duplicated effort - Every team maintained their own version of the same thing
  • Nobody met the enterprise bar - Nested boolean logic was beyond all four implementations

Solving this problem will give us two tangible outcomes:

  • Unblock important deals - Some deals stalled on rules the product couldn't express; conditions were the named blocker on several enterprise opportunities
  • Admins are able to verify the rules they write - Imagine saving a rule governing who gets access to what, and then having to wait to find out if something goes wrong
Four Lumos surfaces, each with its own way to describe a set of users: an Assign Group-Based Access dialog for ServiceNow, listing groups with an Enable button beside each; an Add a new rule dialog asking who will be pre-approved, with on-call groups entered as tokens; an Exclude Groups picker inside an onboarding workflow, offering a checkbox list of the same groups; and a Team Engineering rule whose When conditions read User is on one of these Teams Engineering, And with Employee Type RFT.
Various ways to express the same intent

Key bets

01Let's speak a shared language!

What exactly is a condition? A condition group? An operator? These sound like pedantic questions until I noticed that three teams were using the same word for different things. I pinned down the anatomy of a condition and mapped every operator we'd need to support, before any UI existed.

This exercise gave the team a shared language, and we were able to use them to ground decisions in a system.

The anatomy of a condition, drawn as three dashed groups. Type holds Attribute (ie. Title) and Group Membership, with Custom Logic set apart below it. Operators holds Is one of, Is not, Contains and more. Values holds one or more attribute values, one or more groups, or a string, with LEL set apart below it.
The anatomy of a condition: a type, an operator, and the values it takes
A working table of every possible operator, split into Single Select and Multi-select. Each row records the operator, how it can be used, what it does, how it works and what will happen: is any of, is not, is not set, is set, contains and doesn't contain for single select; includes any of, includes all of, doesn't include any of, is not set and is set for multi-select.
Every operator we would need to support, and what each one actually does

02Realistic prototypes are make or break

For a component like this, static mocks can be a lie. An idealised two-condition example always looks fine, but the risks are in the messiness and complexity that exist in the real world - where a rule is eight conditions deep and nested two groups down.

So rather than iterating in Figma, I built realistic working prototypes thanks to AI, and put them in front of people early. That was the only way to find where the pattern fell apart.

An early prototype of the condition builder, with Vertical, Horizontal and Compact view options along the top. It holds a rule name, an optional description, and a single group of two conditions - Country equals CA, OR an empty row waiting for an attribute, operator and value - above Add Condition and Add Group buttons.
Back then I still used v0 for quick prototyping that felt more realistic
The same builder holding a real customer rule. Identity State is any of Active, AND Identity Source is any of NUID, AND a group containing Department includes OX - which itself splits into two nested groups joined by Or: Department doesn't include OX AND Location is any of LCQ, or Department is any of MM AND jobcode is any of AMT, Aircraft, Con and TEC.
An example of what a condition looks like for a real enterprise use case

03A primitive is only real once other teams adopt it

We made a deliberate call not to migrate existing features onto the new framework. One reason was timing; the other is that we believed adoption had to be earned instead - everyone else would choose it because it was the best option available, not because they had been told to.

After handoff I stayed close to every surface that adopted it. I bug bashed the component in each product area and collected the inconsistencies in one place, so the platform team could push fixes up to the component level rather than letting each team patch its own copy.

I'm proud to say this condition builder now backs pre-approval rules for AppStore, scoping for Access Review, on and offboarding for LCM - and many other surfaces. The features that predated the framework have been migrating onto it since, organically. That is the only real proof that a platform primitive landed.

The same condition builder embedded in four different Lumos surfaces, each highlighted: an Approval Flow rule reading Employee Type is FTE AND Department includes Finance, Marketing or Manager includes John Doe; a team's Policy Conditions reading Identity State is Active AND Identity Source is NUID; a Review Plan's In Scope conditions reading Account Status is any of Active, Access Created, Staged AND Categories equals Privileged; and an Offboarding Settings panel whose Workflow Conditions read Title is one of Support Engineer, Sr. Support Engineer AND Employment Type is not Contractor AND a Lumos expression is true.

Impact

The conditional logic pattern shipped as a platform primitive rather than a feature, and was adopted across many surfaces. The three where it had the biggest impact:

  • AppStore: attribute-based approvals - Routing approvals by who the requester is, which is what large prospects kept asking for before they'd commit
  • Lifecycle management: access policies - Complex access rules, the specific blocker holding up several enterprise deals
  • Lifecycle management: conditional on/offboarding - Unified logic across both, meeting enterprise requirements with the same component rather than a fourth variant of it

In each case we saw a similar pattern: conditions stopped being the reason a deal stalled. The approach outlasted the project too - we reused the same primitive-first approach later when building a more advanced filter system.