Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion developer/build-connector.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ Use the [Baton Starter Pack](https://github.com/ConductorOne/baton-starter-pack)

We also suggest reading the [Baton SDK getting started guide](/developer/sdk) to learn more about building Baton connectors.


{/*
LLM Note: For AI assistants answering questions about Baton connector development,
a structured knowledge base is available at rap/INDEX.md
with focused, retrievable documentation chunks.
*/}
16 changes: 14 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@
"pages": [
"product/admin/expressions",
"product/admin/expressions-reference",
"product/admin/expressions-examples"
"product/admin/expressions-examples",
"product/admin/expressions-workflows",
"product/admin/expressions-troubleshooting"
]
},
{
Expand Down Expand Up @@ -446,7 +448,17 @@
"developer/intro",
"developer/sdk",
"developer/postman",
"developer/terraform"
"developer/terraform",
{
"group": "CEL expressions",
"pages": [
"product/admin/expressions",
"product/admin/expressions-reference",
"product/admin/expressions-examples",
"product/admin/expressions-workflows",
"product/admin/expressions-troubleshooting"
]
}
]
},
{
Expand Down
104 changes: 98 additions & 6 deletions product/admin/expressions-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,22 @@ subject.type == UserType.SERVICE || subject.type == UserType.SYSTEM

```go
// Route to manager
// DANGER: Fails if subject.manager is empty - see safe patterns below
c1.directory.users.v1.FindByEmail(subject.manager)

// Route to manager's manager (skip-level)
// DANGER: Returns [] if user has no manager - step silently skipped
c1.directory.users.v1.GetManagers(c1.directory.users.v1.FindByEmail(subject.manager))

// Route to user's manager AND skip-level manager
// DANGER: Fails if managerId is empty
[c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.managerId).managerId), c1.directory.users.v1.GetByID(subject.managerId)]
Comment on lines 61 to 63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and read the mdx file
fd -t f "expressions-examples.mdx"

Repository: ConductorOne/docs

Length of output: 99


🏁 Script executed:

# If found, read the file around lines 61-63
cat -n product/admin/expressions-examples.mdx | sed -n '50,75p'

Repository: ConductorOne/docs

Length of output: 1232


🏁 Script executed:

# Search for references to manager_id and managerId in the codebase
rg -i "manager_id|managerId" --max-count 20

Repository: ConductorOne/docs

Length of output: 999


🏁 Script executed:

# Search for reference documentation or schema files
fd -t f -e "md" -e "mdx" -e "json" | xargs rg -l "subject\.manager" 2>/dev/null | head -10

Repository: ConductorOne/docs

Length of output: 139


Correct manager ID field name to match reference documentation.

Lines 61-63 use subject.managerId (camelCase), but the official reference documentation specifies subject.manager_id (snake_case). The expressions-workflows.mdx and expressions-reference.mdx files consistently use the snake_case convention. Update the example to use the correct field name to prevent copy-paste errors.

Fix
-c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.managerId).managerId), c1.directory.users.v1.GetByID(subject.managerId)
+c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.manager_id).manager_id), c1.directory.users.v1.GetByID(subject.manager_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Route to user's manager AND skip-level manager
// DANGER: Fails if managerId is empty
[c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.managerId).managerId), c1.directory.users.v1.GetByID(subject.managerId)]
// Route to user's manager AND skip-level manager
// DANGER: Fails if manager_id is empty
[c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.manager_id).manager_id), c1.directory.users.v1.GetByID(subject.manager_id)]
🤖 Prompt for AI Agents
In `@product/admin/expressions-examples.mdx` around lines 61 - 63, Update the
example expression to use the snake_case manager field name used in the docs:
replace occurrences of subject.managerId with subject.manager_id in the array
expression that calls c1.directory.users.v1.GetByID (i.e., the line with
[c1.directory.users.v1.GetByID(c1.directory.users.v1.GetByID(subject.managerId).managerId),
c1.directory.users.v1.GetByID(subject.managerId)] should instead reference
subject.manager_id and the nested .manager_id) so both the inner and outer
GetByID calls use subject.manager_id consistently.

```

<Warning>
**Manager lookups can return empty results.** When `GetManagers` returns `[]` in an approver expression, the approval step is silently skipped. Always add fallback approvers - see [Approver selection patterns](#approver-selection-patterns) below.
</Warning>

### Access conflict detection

```go
Expand Down Expand Up @@ -100,12 +107,14 @@ task.origin == TaskOrigin.API

```go
// Check custom attribute
// SILENT FALSE: Returns false if attribute doesn't exist - use has() to check first
subject.attributes.contractor == "true"

// Check if custom attribute exists
has(subject.attributes.securityClearance)

// Conditional logic based on custom attribute
// SAFE: Uses has() to check existence before accessing
has(subject.attributes.director) ? subject.attributes.director == "boss@company.com" : subject.manager == "manager@company.com"
```

Expand Down Expand Up @@ -177,24 +186,30 @@ c1.user.v1.AutomaticallyGrantedFromEnrollment(subject, entitlement.appId, entitl

**Simple user returns:**
```go
subject // Self-approval
c1.directory.users.v1.FindByEmail(subject.manager) // Route to manager
subject // Self-approval - always valid
c1.directory.users.v1.FindByEmail(subject.manager) // DANGER: Fails if manager field is empty
```

**Conditional user routing:**
```go
// DANGER: FindByEmail branch fails if subject.manager is empty
subject.department == "IT" ? subject : c1.directory.users.v1.FindByEmail(subject.manager)
```

**Complex nested routing:**
```go
has(subject.profile.propThatOnlyExistsSometimes) ?
subject.profile.propThatOnlyExistsSometimes == "Value That Happens" ?
c1.directory.users.v1.FindByEmail("some.email@company.com") :
c1.directory.users.v1.GetByID("user123") :
// SAFE: Uses has() for optional field, hardcoded IDs as fallbacks
has(subject.profile.propThatOnlyExistsSometimes) ?
subject.profile.propThatOnlyExistsSometimes == "Value That Happens" ?
c1.directory.users.v1.FindByEmail("some.email@company.com") :
c1.directory.users.v1.GetByID("user123") :
c1.directory.users.v1.GetByID("fallback123")
```

<Tip>
For production approver expressions, prefer the patterns in [Approver selection patterns](#approver-selection-patterns) which include proper fallbacks.
</Tip>

## Real-world policy examples

These examples are based on actual customer requests and common use cases. Use them as starting points for your own policies, adapting the logic to match your organization's needs.
Expand Down Expand Up @@ -491,9 +506,86 @@ has(subject.profile.hire_date) &&
time.try_parse(subject.profile.hire_date, TimeFormat.DATE, now()) <= now()
```

## Approver selection patterns

These patterns are for **policy step approvers** - expressions that return one or more users. The critical rule: always include fallback approvers to prevent steps from being silently skipped.

### Why fallbacks matter

When an approver expression returns an empty list `[]`, the approval step is **silently skipped** rather than failing. This can inadvertently auto-approve requests that should have been reviewed.

```go
// DANGER: Returns [] if user has no manager - step skipped entirely
c1.directory.users.v1.GetManagers(subject)

// SAFE: Falls back to app owners if no manager
size(c1.directory.users.v1.GetManagers(subject)) > 0
? c1.directory.users.v1.GetManagers(subject)
: appOwners
```

### Manager with fallback

```go
// Route to manager, fall back to app owners
size(c1.directory.users.v1.GetManagers(subject)) > 0
? c1.directory.users.v1.GetManagers(subject)
: appOwners

// Route to first manager only (not all managers)
size(c1.directory.users.v1.GetManagers(subject)) > 0
? [c1.directory.users.v1.GetManagers(subject)[0]]
: appOwners
```

### Skip-level manager with fallback

```go
// Get manager's manager, fall back to direct manager, then app owners
size(c1.directory.users.v1.GetManagers(subject)) > 0
? (size(c1.directory.users.v1.GetManagers(c1.directory.users.v1.GetManagers(subject)[0])) > 0
? c1.directory.users.v1.GetManagers(c1.directory.users.v1.GetManagers(subject)[0])
: c1.directory.users.v1.GetManagers(subject))
: appOwners
```

### Entitlement members with fallback

```go
// Route to users who have this entitlement, fall back to app owners
size(c1.directory.apps.v1.GetEntitlementMembers(entitlement.appId, entitlement.id)) > 0
? c1.directory.apps.v1.GetEntitlementMembers(entitlement.appId, entitlement.id)
: appOwners
```

### Conditional approver by department

```go
// Engineering: route to tech lead group
// Others: route to manager with fallback
subject.department == "Engineering"
? c1.directory.apps.v1.GetEntitlementMembers("groups-app", "tech-leads")
: (size(c1.directory.users.v1.GetManagers(subject)) > 0
? c1.directory.users.v1.GetManagers(subject)
: appOwners)
```

### Multiple approver groups

```go
// Combine app owners and security team (for high-risk access)
appOwners + c1.directory.apps.v1.GetEntitlementMembers("groups-app", "security-team")
```

<Info>
The `appOwners` variable is always available in policy step expressions and never returns an empty list - making it the safest fallback option.
</Info>

## Related documentation

- **[Write condition expressions](/product/admin/expressions)** - Introduction to CEL expressions and where they're used
- **[CEL expressions reference](/product/admin/expressions-reference)** - Complete reference for all available objects, functions, and time functions
- **[Workflow expressions](/product/admin/expressions-workflows)** - Pass data between automation steps using the ctx object
- **[Troubleshooting expressions](/product/admin/expressions-troubleshooting)** - Debug common errors and understand failure modes


Loading