Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .github/workflows/test-helpers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ jobs:
- name: Run pytest
run: pytest tests/tasks/uipath-agents/ -v

pytest-review-check:
runs-on: uipath-ubuntu-latest
name: uipath-review checker unit tests
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.13'

- name: Install pytest
run: pip install pytest

- name: Run pytest
run: pytest tests/tasks/uipath-review/ -v

runtime-payload-casing-guard:
runs-on: uipath-ubuntu-latest
name: runtime-payload key-casing contract guard
Expand Down
1 change: 1 addition & 0 deletions docs/REQUIRED-CHECKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ This table is machine-read. `scripts/parse-required-checks.py` is its only parse
| `maestro-bpmn checker unit tests` | `test-helpers.yml` |
| `maestro-case checker unit tests` | `test-helpers.yml` |
| `uipath-agents checker unit tests` | `test-helpers.yml` |
| `uipath-review checker unit tests` | `test-helpers.yml` |
| `uipath-planner checker unit tests` | `test-helpers.yml` |
| `uipath-admin verify negative controls` | `test-helpers.yml` |
| `runtime-payload key-casing contract guard` | `test-helpers.yml` |
Expand Down
178 changes: 178 additions & 0 deletions tests/tasks/uipath-agents/_fixtures/ByogMockCli/mocks/uip
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Selective `uip` shim for the BYOG guardrail tasks.

Shared by `coded/guardrails/byog_middleware`, `coded/guardrails/byog_decorator`
and `lowcode/guardrails/byog_pinning` -- one payload, so a CLI field rename is a
one-file edit and the three tasks cannot drift apart. Serves the two read-only
discovery paths an agent walks before it can pin a bring-your-own guardrail,
both answering with the configuration the prompts name (`byog-smoke-agent-pin`,
pii_detection):

* `uip agent guardrails list [--byo]` -> GuardrailDefinitionsList
* `uip guardrails byo-configurations list` -> ByoGuardrailConfigurationsList

Without `--byo`, `agent guardrails list` also returns the BUILT-IN
`pii_detection` entry sharing the same `Validator` name, so the agent must
disambiguate via `IsByo`/`ByoValidatorName` exactly as the skill teaches
rather than grabbing the first PII row. The two views agree on ids and
connector metadata, so cross-checking one against the other is consistent.

Every other invocation exec's the REAL `uip` found later on PATH, and
`--help` is handed to the real CLI so flag questions get real answers
(a canned payload there reads as "that flag does not exist").

Why mock: the graded behavior is code wiring in `graph.py`, and both
checkers are pure AST analysis that never touch a tenant -- only the
agent's DISCOVERY step needed one. It could not keep depending on the
shared smoke tenant. `byo-configurations create` probes the connection
server-side with no skip flag, so a configuration cannot be seeded
per-run, and the tenant has no guardrail-capable connection to bind
(same reasoning as the byog_pinning mock, which fixed the low-code half
of this family). Left live, these tasks were graded on whether an
external tenant happened to hold a fixture: on 2026-08-24 the tenant
held three configurations, none of them `byog-smoke-agent-pin`; by
2026-09-10 it held none at all, and both discovery calls returned
`Data: []`. The agent then correctly refused to fabricate a validator
name and changed nothing, scoring 0.00 -- a fixture outage rendered as a
skill regression.

`mock_path_dirs: [mocks]` PATH-prepends this script's directory inside the
sandbox. Linux smoke only (no `windows` tag on any of the three tasks), hence no
`uip.cmd` twin -- same as the ixp and platform guardrails mocks.
"""

import json
import os
import shutil
import sys

MOCK_DIR = os.path.dirname(os.path.abspath(__file__))

VALIDATOR_NAME = "byog-smoke-agent-pin"
CONNECTION_ID = "18fb337c-29b7-4162-a9e8-0c05b01cf4df"
CONFIGURATION_ID = "e5723bb8-fbc2-4317-c7d7-08de803bc010"
CONNECTOR_NAME = "Azure AI Content Safety"
CONNECTOR_KEY = "uipath-azure-contentsafety"

PII_PARAMETERS = [
{
"Type": "enum-list",
"Id": "entities",
"Required": True,
"DefaultValue": ["Email", "PhoneNumber"],
"Options": [
"Email",
"PhoneNumber",
"Person",
"Address",
"USSocialSecurityNumber",
],
"DisplayName": "Entities",
"Description": "PII entity types to detect.",
},
{
"Type": "map-enum",
"Id": "entityThresholds",
"Required": True,
"DefaultValue": {"Email": 0.5, "PhoneNumber": 0.5},
"KeySource": "entities",
"Min": 0,
"Max": 1,
"DisplayName": "Per-entity threshold",
"Description": "Confidence threshold (0-1) per selected entity.",
},
]

BYO_ENTRY = {
"Validator": "pii_detection",
"IsByo": True,
"Status": "Available",
"AllowedScopes": ["Agent", "Llm", "Tool"],
"GuardrailStages": {
"Agent": ["PreExecution", "PostExecution"],
"Llm": ["PreExecution", "PostExecution"],
"Tool": ["PreExecution", "PostExecution"],
},
"DisplayName": "PII Detection",
"Description": (
"Detects personally identifiable information. Served by the "
"tenant's external bring-your-own guardrail provider."
),
"ByoValidatorName": VALIDATOR_NAME,
"ByoConnectionId": CONNECTION_ID,
"ByoConfigurationId": CONFIGURATION_ID,
"ByoConnectorName": CONNECTOR_NAME,
"ByoConnectorKey": CONNECTOR_KEY,
"FolderKey": "627fe423-5c73-464a-abff-41fdaad6ac19",
"Parameters": PII_PARAMETERS,
}

BUILTIN_ENTRY = {
"Validator": "pii_detection",
"IsByo": False,
"Status": "Available",
"AllowedScopes": ["Agent", "Llm", "Tool"],
"GuardrailStages": {
"Agent": ["PreExecution", "PostExecution"],
"Llm": ["PreExecution", "PostExecution"],
"Tool": ["PreExecution", "PostExecution"],
},
"DisplayName": "PII Detection",
"Description": (
"Detects personally identifiable information using Azure "
"Cognitive Services (UiPath built-in)."
),
"Parameters": PII_PARAMETERS,
}

# The admin-side view of the same record. Field names mirror the CLI's own
# documented examples for `guardrails byo-configurations`, so the fields the
# skill teaches are the fields that come back.
CONFIGURATION = {
"Id": CONFIGURATION_ID,
"ConnectionId": CONNECTION_ID,
"ValidatorName": VALIDATOR_NAME,
"ValidatorType": "pii_detection",
"FallbackOnUiPath": False,
"Enabled": True,
"CreatedAt": "2026-07-02T11:40:00Z",
"UpdatedAt": None,
"ConnectorKey": CONNECTOR_KEY,
"ConnectorName": CONNECTOR_NAME,
"ConnectionName": f"{CONNECTOR_NAME} - Prod",
"ValidConnection": True,
}


def real_uip():
path = os.environ.get("PATH", "")
parts = [p for p in path.split(os.pathsep) if p and os.path.abspath(p) != MOCK_DIR]
return shutil.which("uip", path=os.pathsep.join(parts))


def emit(code, data):
print(json.dumps({"Result": "Success", "Code": code, "Data": data}, indent=2))
return 0


def main():
args = sys.argv[1:]
literal = [a for a in args if not a.startswith("-")]

# A help request is a question about flags, not about tenant data.
if "--help" not in args and "-h" not in args:
if literal[:3] == ["agent", "guardrails", "list"]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 minor — second copy of this payload, and the twin is now the weaker one.

tests/tasks/uipath-agents/lowcode/guardrails/byog_pinning/mock_template/mocks/uip carries the same BYO_ENTRY and BUILTIN_ENTRY, the same GUIDs, the same validator name. This file adds two things it does not have:

  • guardrails byo-configurations list, so a low-code agent that follows the ByoConfigurationId cross-reference at lowcode/.../guardrails.md:642 still falls through to the real CLI and gets Data: [], the exact fixture-outage failure this PR is fixing on the coded half.
  • the --help passthrough, so byog_pinning still answers a flag question with a canned payload.

Two copies that already disagree will keep disagreeing, and a CLI field rename now needs both edited. Suggest pointing byog_pinning.yaml at this fixture and deleting its local copy (it already reaches across with ../_fixtures/..., though I have not verified the harness accepts a path that climbs out of lowcode/), or hoisting the shim to a shared tests/tasks/uipath-agents/_fixtures/.

Not blocking, and either way this file is the better of the two.

data = [BYO_ENTRY] if "--byo" in args else [BUILTIN_ENTRY, BYO_ENTRY]
return emit("GuardrailDefinitionsList", data)
if literal[:3] == ["guardrails", "byo-configurations", "list"]:
return emit("ByoGuardrailConfigurationsList", [CONFIGURATION])

real = real_uip()
if not real:
sys.stderr.write("uip (shim): real uip CLI not found on PATH\n")
return 127
os.execv(real, [real] + args)


if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ task_id: skill-agent-guardrail-coded-byog-decorator
description: >
Decorator-style bring-your-own guardrail (BYOG) for a coded agent. A simple
LangGraph customer support agent is seeded with no guardrails; it already has a
create_support_agent() factory. The tenant has a BYOG configuration registered
(admin-side); the user asks to route the agent's PII checks through it using the
decorator style. Verifies the agent stacks @guardrail with ByoValidator — pinned
create_support_agent() factory. A mocked `uip` serves the BYOG configuration the
prompt names, so discovery answers the same way on every run; the user asks to
route the agent's PII checks through it using the decorator style.
Verifies the agent stacks @guardrail with ByoValidator — pinned
to the configuration by its validator name, blocking on violations — over a real
factory function rather than reaching for the built-in PII validator.
tags: [uipath-agents, e2e, coded, mode:build, lifecycle:edit, guardrail]
Expand All @@ -21,7 +22,13 @@ agent:
disallowed_tools: ["Task"]

sandbox:
# Discovery is mocked, so the task no longer depends on a fixture living on the
# shared smoke tenant. See _fixtures/ByogMockCli/mocks/uip for why that
# dependency was untenable and what the shim serves.
mock_path_dirs: [mocks]
template_sources:
- type: template_dir
path: ../../../_fixtures/ByogMockCli
- type: template_dir
path: ../_fixtures/SimpleCodedAgent

Expand All @@ -47,6 +54,14 @@ success_criteria:
weight: 1.5
pass_threshold: 1.0

- type: command_executed
description: "Agent discovered the BYO configuration from the CLI rather than copying the name out of the prompt"
tool_name: "Bash"
command_pattern: '(uip|\$UIP)\s+(agent\s+guardrails\s+list|guardrails\s+byo-configurations\s+list)'
min_count: 1
weight: 2.0
pass_threshold: 1.0

- type: run_command
description: "BYOG decorator guardrail correctly added to graph.py"
command: "python3 $TASK_DIR/check_byog_decorator.py"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
task_id: skill-agent-guardrail-coded-byog-middleware
description: >
Middleware-style bring-your-own guardrail (BYOG) for a coded agent. A simple
LangGraph customer support agent is seeded with no guardrails. The tenant already
has a BYOG configuration registered (admin-side); the user asks to route the
agent's PII checks through it. Verifies the agent wires
LangGraph customer support agent is seeded with no guardrails. A mocked `uip`
serves the BYOG configuration the prompt names, so discovery answers the same
way on every run; the user asks to route the agent's PII checks through it.
Verifies the agent wires
UiPathByoGuardrailMiddleware — pinned to the configuration by validator_name,
spread into create_agent(), with a block action — rather than reaching for the
built-in PII validator.
Expand All @@ -21,7 +22,13 @@ agent:
disallowed_tools: ["Task"]

sandbox:
# Discovery is mocked, so the task no longer depends on a fixture living on the
# shared smoke tenant. See _fixtures/ByogMockCli/mocks/uip for why that
# dependency was untenable and what the shim serves.
mock_path_dirs: [mocks]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 minor — nothing grades the discovery this mock exists to serve.

The low-code sibling pairs its shim with a criterion (byog_pinning.yaml:44, weight 2.0):

  - type: command_executed
    description: "Agent discovered the BYO entry via uip agent guardrails list (--byo or full list)"
    tool_name: "Bash"
    command_pattern: 'uip\s+agent\s+guardrails\s+list'
    min_count: 1

Here there is no such criterion, and the prompt already names byog-smoke-agent-pin, so check_byog_middleware.py passes whether the agent discovered the configuration or copied the string out of the prompt. Two consequences:

  • The mock is ungraded infrastructure. If it stops being installed, and the description itself documents one way that happens (the runbook's -D 'sandbox.mock_path_dirs=["."]' clobber), nothing reports it. The task just drifts back to depending on the agent's mood about verifying. That is the same shape as the byog_decorator carry-forward this PR is fixing: an environment fault wearing a skill result.
  • Coded Rule 18 goes untested. guardrails.md:490 says to read ByoValidatorName from discovery and never from memory, and step 2 says cross-check Enabled/ValidConnection. The shim now serves both verbs specifically so the agent can do that. Nothing checks that it did.

Suggest adding the byog_pinning criterion to both this task and byog_decorator.yaml, matching on either verb.

template_sources:
- type: template_dir
path: ../../../_fixtures/ByogMockCli
- type: template_dir
path: ../_fixtures/SimpleCodedAgent

Expand All @@ -39,6 +46,14 @@ initial_prompt: |
end-to-end in a single pass.

success_criteria:
- type: command_executed
description: "Agent discovered the BYO configuration from the CLI rather than copying the name out of the prompt"
tool_name: "Bash"
command_pattern: '(uip|\$UIP)\s+(agent\s+guardrails\s+list|guardrails\s+byo-configurations\s+list)'
min_count: 1
weight: 2.0
pass_threshold: 1.0

- type: run_command
description: "BYOG middleware guardrail correctly added to graph.py"
command: "python3 $TASK_DIR/check_byog_middleware.py"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ task_id: skill-agent-guardrail-byog-pinning
description: >
BYO guardrail pinning on an existing agent. The sandbox is pre-seeded
with the Web Research Briefing solution (shared _fixtures copy,
guardrails empty), and a selective `uip` shim (mock_template/mocks/uip)
guardrails empty), and a selective `uip` shim (../../../_fixtures/ByogMockCli/mocks/uip)
serves the discovery call — `uip agent guardrails list` returns a BYO
pii_detection entry named byog-smoke-agent-pin alongside the built-in
entry sharing the same Validator name — while every other uip command
Expand All @@ -23,7 +23,7 @@ run_limits:
sandbox:
mock_path_dirs: [mocks]
template_sources:
- {type: template_dir, path: mock_template}
- {type: template_dir, path: ../../../_fixtures/ByogMockCli}
- {type: template_dir, path: ../_fixtures/WebResearchBriefingSolution}

initial_prompt: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Validates that the agent authored a builtInValidator guardrail for
pii_detection in agent.json that is pinned to the BYO configuration the
mocked discovery served (see mock_template/mocks/uip):
mocked discovery served (see ../../../_fixtures/ByogMockCli/mocks/uip):

- guardrails array exists and is non-empty
- At least one guardrail has $guardrailType == "builtInValidator"
Expand Down
Loading
Loading