Skip to content

Commit 56ebd7c

Browse files
feat(uipath-audit): add audit skill
Skill teaches agents how to drive `uip admin audit org|tenant {sources|events|export}` from natural-language audit / investigation prompts. Mirrors the structure of the existing uipath-gov-aops-policy skill. Adds: - skills/uipath-audit/SKILL.md - skills/uipath-audit/references/audit-commands.md - skills/uipath-audit/references/audit-workflow-guide.md - tests/tasks/uipath-audit/{audit_sources_smoke,audit_events_pagination_smoke,audit_export_e2e}.yaml - CODEOWNERS entry Drive-by: hooks/ensure-uip.sh now respects SKIP_UIP_AUTO_INSTALL=1 so contributors developing CLI features against a `bun link`'d workspace binary don't have their link clobbered every session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e25691b commit 56ebd7c

8 files changed

Lines changed: 1015 additions & 0 deletions

File tree

CODEOWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,7 @@
8383
# Tasks skill (Action Center Operate)
8484
/skills/uipath-tasks/ @UiPath/ActionCenterDev
8585
/tests/tasks/uipath-tasks/ @UiPath/ActionCenterDev
86+
87+
# Audit skill (CLI: sources / events / export at org & tenant scope)
88+
/skills/uipath-audit/ @yadvender-uipath @CalinMPopa @chandhanshanth
89+
/tests/tasks/uipath-audit/ @yadvender-uipath @CalinMPopa @chandhanshanth

hooks/ensure-uip.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66

77
set -e
88

9+
# Opt-out for contributors developing CLI features against a `bun link`'d
10+
# workspace binary — the published @uipath/cli on npm doesn't yet have
11+
# their unmerged commands, and re-installing it every session clobbers
12+
# the workspace link.
13+
if [ -n "$SKIP_UIP_AUTO_INSTALL" ]; then
14+
echo "SKIP_UIP_AUTO_INSTALL is set; skipping global install of @uipath/cli + @uipath/rpa-tool." >&2
15+
exit 0
16+
fi
17+
918
# ── helpers ──────────────────────────────────────────────────────────
1019
fail() {
1120
echo "$1" >&2

skills/uipath-audit/SKILL.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
name: uipath-audit
3+
description: "UiPath audit events — list sources, query events with cursor pagination, and export ZIPs from the long-term store via `uip admin audit org|tenant {sources|events|export}`. For governance policies→uipath-gov-aops-policy."
4+
allowed-tools: Bash, Read, Write, Grep, Glob
5+
---
6+
7+
# UiPath Audit
8+
9+
> **Preview** — skill is under active development; surface and behavior may change.
10+
11+
Skill for querying and exporting UiPath audit events via `uip admin audit`. Covers six commands across two scopes (`org` and `tenant`) and three verbs (`sources`, `events`, `export`). Use when an admin asks "who did X to Y", "show me logins last week", or "give me an audit dump for January".
12+
13+
## When to Use This Skill
14+
15+
Activate on both **explicit audit requests** and **natural-language investigation intent** — users rarely say "audit events" by name.
16+
17+
**Explicit requests:**
18+
19+
- User asks about `uip admin audit` commands.
20+
- User asks to list audit event sources / targets / types.
21+
- User asks to query, filter, paginate, or export audit events.
22+
- User wants a CSV/ZIP dump of audit history for a window.
23+
24+
**Investigation intent (rule/policy patterns without naming the product):**
25+
26+
- "Who deleted the X folder last Tuesday?"
27+
- "Show me failed logins for user Y this month."
28+
- "What changed on tenant Z between Jan 1 and Feb 1?"
29+
- "Give me the audit log for the last 30 days."
30+
- "Was the API key rotated by someone in our org?"
31+
- "Export everything for compliance for Q4."
32+
33+
## Recognize Audit Intent → Pick a Scope
34+
35+
The `org` vs `tenant` choice matters — they hit different basePaths and surface different events.
36+
37+
| User says... | Likely scope | Why |
38+
|---|---|---|
39+
| "who joined / left the organization", "who was made an admin", "license changes", "cross-tenant audit" | **org** | Org-level admin events (memberships, license, tenant lifecycle) live under `/orgaudit_`. |
40+
| "what happened on tenant X", "logins on this tenant", "policy changes within a tenant", "asset/queue/folder edits" | **tenant** | Tenant-scoped events (Orchestrator, AOps, AI Trust, etc.) live under `/{tenantId}/tenantaudit_`. |
41+
| "everything everywhere" | **both** — run the same flow once per scope and present combined results. |
42+
43+
### Disambiguation rule — never silently default
44+
45+
If the user's prompt is **vague about scope** (e.g. "export the audit log", "show me events", "give me sources") AND the conversation has **no prior turn that established scope or tenant**, **stop and ask** before running any command. Do not assume `tenant` just because it's the more common case — silently picking the wrong scope means the user gets data they didn't intend (org events when they wanted tenant, or vice versa) and they may not realize it for hours.
46+
47+
Wording template (use one yes/no question, two clarifications max):
48+
49+
> Quick check before I run this — should this be at:
50+
>
51+
> - **`org` scope** (organization-level admin events: memberships, license, tenant lifecycle), or
52+
> - **`tenant` scope** (events scoped to a specific tenant)?
53+
>
54+
> If `tenant`, which tenant? (I see `<tenantA>`, `<tenantB>` in your login context — or pass an explicit GUID.)
55+
56+
When to **skip the question** and proceed:
57+
58+
1. **Scope is explicit in the current prompt** — the user wrote "tenant", "org", a tenant name, or a clear org-level phrase from the table above. Use it directly.
59+
2. **Scope was established earlier in this conversation** — e.g. the user said "let's investigate tenant Acme-Prod" three turns ago and is now asking "and now export it." Reuse that scope and tenant; don't re-ask. Do mention which scope you're using in the response so the user can correct if they meant to switch.
60+
3. **The login context disambiguates trivially** — only one tenant is selected AND the intent table strongly matches tenant. Even then, name the tenant in your response so the user can catch a mismatch.
61+
62+
If you ask and the user names a tenant by display name (not GUID), resolve it via the login context before passing `--tenant-id`. If you can't resolve it, ask for the GUID rather than guessing.
63+
64+
## Critical Rules
65+
66+
1. **Always run `uip login status --output json` first.** If `loginStatus !== "Logged in"`, surface the hint and stop. Never auto-`uip login` — interactive OAuth blocks an automation.
67+
2. **Pick scope before any other call — and don't silently default.** Use the [Disambiguation rule](#disambiguation-rule--never-silently-default) above: if the prompt is vague AND there's no prior conversational context, **ask** which scope (and which tenant if `tenant`). Only proceed without asking when scope is explicit in the prompt, established earlier in the conversation, or unambiguously implied by login context AND intent. `tenant` requires either a tenant in the login context or `--tenant-id <guid>`. `org` ignores `--tenant-id`.
68+
3. **Discover source IDs with `sources` before filtering `events` by `--source/--target/--type`.** Never invent GUIDs — the SDK won't help you guess.
69+
4. **`events` returns `{ auditEvents, next, previous }` — NOT a bare array.** Do not assume `Data` is iterable directly. The cursor naming is **chronological**: `next` = newer, `previous` = older. Walking newest-backward (the default) follows `previous`.
70+
5. **`events` server-clamps `maxCount` to `[10, 200]`.** When the user wants more than 200, the tool paginates internally — pass `--limit N` and the tool fetches `ceil(N/200)` pages. Do not re-implement pagination in the agent.
71+
6. **`export` writes a ZIP from the long-term store.** `--from-date` and `--to-date` are required and ISO 8601. Always use `--output-file <path>`. Never overwrite a path the user did not explicitly approve.
72+
7. **Pass `--output json` on every `sources` and `events` call** when the response is being parsed. `export` does not need it (the JSON envelope is just metadata; the ZIP goes to `--output-file`).
73+
8. **Stop and surface the error if any command fails.** Do not retry on auth errors — `Audit.Read` scope must already be on the bearer token; retrying does nothing useful.
74+
9. **Use ISO 8601 for time bounds.** Date-only (`2026-04-01`) or with time (`2026-04-01T14:30:00Z`). Always UTC when in doubt — the audit service stores and returns UTC.
75+
76+
## Quick Start
77+
78+
These steps are for the canonical "find events in a window then export" flow. For specific scenarios jump to the [Task Navigation](#task-navigation) table.
79+
80+
### Step 0 — Verify the `uip` CLI
81+
82+
```bash
83+
which uip && uip --version
84+
```
85+
86+
If not installed:
87+
88+
```bash
89+
npm install -g @uipath/cli
90+
```
91+
92+
### Step 1 — Check login status
93+
94+
```bash
95+
uip login status --output json
96+
```
97+
98+
If `loginStatus !== "Logged in"`, instruct the user to run `uip login` and stop. The audit service rejects calls in 5ms at the gateway when the token's `aud` claim is missing `Audit`.
99+
100+
### Step 1.5 — Confirm scope (skip only if already explicit or established earlier in the conversation)
101+
102+
If the prompt didn't specify `org` vs `tenant` (and which tenant), pause and ask once before running anything. See the [Disambiguation rule](#disambiguation-rule--never-silently-default) for the canonical wording. Stating the scope in your reply (e.g. "Running tenant export against `Acme-Prod`…") gives the user one last chance to correct the assumption even when it was clearly implied.
103+
104+
### Step 2 — Discover sources at the right scope
105+
106+
Run **once** at the start of an investigation; cache the result for this session.
107+
108+
```bash
109+
# Tenant-scoped (most common)
110+
uip admin audit tenant sources --output json > sources.json
111+
112+
# Org-scoped (admin events: tenant lifecycle, license, memberships)
113+
uip admin audit org sources --output json > sources-org.json
114+
```
115+
116+
Each entry has `id` (a GUID — pass to `events --source`), `name` (human-readable), and `eventTargets[]` (each with their own GUIDs and `eventTypes[]`).
117+
118+
### Step 3 — Query events with filters
119+
120+
```bash
121+
# Filter by source ID + 7-day window, limit 50
122+
uip admin audit tenant events \
123+
--source <SOURCE_GUID_FROM_STEP_2> \
124+
--from-date 2026-04-22T00:00:00Z \
125+
--to-date 2026-04-29T00:00:00Z \
126+
--limit 50 \
127+
--output json
128+
```
129+
130+
The response shape:
131+
132+
```json
133+
{
134+
"Result": "Success",
135+
"Code": "AuditTenantEvents",
136+
"Data": {
137+
"auditEvents": [ ... ],
138+
"next": null, // chronologically newer (often null at "now")
139+
"previous": "/...?before=...&beforeId=..." // chronologically older — the next page going back in time
140+
}
141+
}
142+
```
143+
144+
For more than 200 events, just pass `--limit 500` (or larger) — the tool paginates internally. Do not write a manual loop on `--from-date`/`--to-date` in the agent.
145+
146+
### Step 4 — Export for compliance / sharing
147+
148+
```bash
149+
uip admin audit tenant export \
150+
--from-date 2026-01-01 \
151+
--to-date 2026-02-01 \
152+
--output-file ./audit-jan.zip
153+
```
154+
155+
The tool issues one HTTP call per UTC day inside the window and aggregates the responses into a single flat ZIP at `--output-file`. The result envelope reports `{Path, Bytes, Format: "zip", Days, NonEmptyDays}`. On any chunk failure (e.g. HTTP 504), no file is written and the error identifies which day failed.
156+
157+
## Anti-patterns
158+
159+
- **Do NOT** assume `events` returns a bare array. It's `{auditEvents, next, previous}`.
160+
- **Do NOT** loop on `--from-date`/`--to-date` to "paginate" — that's not what those flags do. Just bump `--limit` and the CLI handles cursor pagination internally.
161+
- **Do NOT** silently default to `tenant` (or `org`) when the user's prompt is ambiguous about scope. Ask which scope, and for tenant scope which tenant, **before** running anything. The exception is when the conversation already established scope on a prior turn.
162+
- **Do NOT** invent source/target/type GUIDs. Always discover via `sources` first.
163+
- **Do NOT** call `events` with no time bound on a noisy tenant — the response will be huge and unanchored. Default to a bounded window.
164+
- **Do NOT** pass `--tenant-id` to `org`-scoped commands — it's silently ignored. If you find yourself doing this, you probably meant `tenant` scope.
165+
- **Do NOT** retry on 401 auth errors. The token is missing the `Audit.Read` scope; retrying produces the same failure. Tell the user to `uip logout && uip login` so the new scope is included.
166+
- **Do NOT** export into a directory that doesn't exist without confirming with the user. The tool will create parent dirs but the path itself must be one the user approved.
167+
- **Do NOT** assume per-day files inside the export ZIP are CSV — they are JSON arrays with PascalCase keys (`Id`, `CreatedOn`, `EventType`…). The CLI does not currently re-format them. If a CSV is needed, that's a post-processing step on the agent's side.
168+
169+
## Task Navigation
170+
171+
| I need to... | Read these |
172+
|---|---|
173+
| **Find who did X to resource Y** | Quick Start — Steps 1–3, with `--source` and `--target` from the `sources` discovery output |
174+
| **Show login history for user X** | [audit-workflow-guide.md — Investigation 2](./references/audit-workflow-guide.md) |
175+
| **Export a date-range dump** | Quick Start — Step 4, or [audit-workflow-guide.md — Investigation 3](./references/audit-workflow-guide.md) |
176+
| **Compare org-level vs tenant-level activity** | [audit-workflow-guide.md — Investigation 4](./references/audit-workflow-guide.md) |
177+
| **Look up command flags / output shapes** | [audit-commands.md](./references/audit-commands.md) |
178+
| **Paginate beyond 200 events** | [audit-commands.md — events flag table](./references/audit-commands.md), Critical Rule #5 |
179+
| **Recognize a scope from natural-language intent** | Intent table at the top of this SKILL.md |
180+
181+
## Key Concepts
182+
183+
### Scope determines basePath, not query params
184+
185+
- `org``{baseUrl}/{orgId}/orgaudit_/api/Query/...`
186+
- `tenant``{baseUrl}/{orgId}/{tenantId}/tenantaudit_/api/Query/...`
187+
188+
Same `QueryApi` underneath; the only difference is which segment the SDK puts in the URL.
189+
190+
### Cursor pagination is chronological, not pagination-order
191+
192+
| Cursor | Direction | When you'd follow it |
193+
|---|---|---|
194+
| `next` | events **newer** than the current page | rare — only useful when starting from a historical anchor and walking forward |
195+
| `previous` | events **older** than the current page | common — paginating from "now" backwards through history |
196+
197+
The CLI tool follows `previous` automatically when `--limit > 200`. If you're driving cursor-by-cursor manually, follow `previous` for "load more older."
198+
199+
### `events` `Data` shape vs `sources`/`export`
200+
201+
| Verb | `Data` shape |
202+
|---|---|
203+
| `sources` | array of `AuditEventSourceDto` |
204+
| `events` | object `{auditEvents, next, previous}` |
205+
| `export` | object `{Path, Bytes, Format, Days, NonEmptyDays}` |
206+
207+
`events` is the one verb that legitimately returns an object — pagination cursors live alongside the rows. Make sure consumers handle that, not a bare array.
208+
209+
### Status codes
210+
211+
`AuditEventStatus` is a 0/1 enum on the wire. Use the labels at the CLI surface:
212+
213+
- `--status Success` (or `0`) — successful operations
214+
- `--status Failure` (or `1`) — failed operations
215+
216+
Case-insensitive, validated client-side before any API call.
217+
218+
## Completion Output
219+
220+
When you finish a query or export, report:
221+
222+
1. **Operation & result** — e.g. `Found 47 audit events on tenant T in the last 7 days` or `Wrote 123,456 bytes to /path/to/audit.zip (3 days, 2 non-empty)`.
223+
2. **Scope used** (`org` or `tenant`) and any `--tenant-id` override.
224+
3. **Time window** — explicit ISO bounds, even if they came from a relative phrase ("last 7 days").
225+
4. **Filters applied** — sources, types, users, status. Important for reproducibility.
226+
5. **Cursor state** — for `events`, mention whether `previous` is null (the user has reached the start of audit history for the filter) or populated (more older events available — re-run with a larger `--limit` to fetch more; the CLI paginates internally).
227+
6. **Next step** — "Want me to widen the window?", "Want me to export this slice?", "Want me to filter by user X?". Wait for the user's choice; do not chain mutations.
228+
229+
## References
230+
231+
- **[audit-commands.md](./references/audit-commands.md)** — Single source of truth for every `uip admin audit` subcommand: signature, every flag with required/optional, the `Code` value, and the exact `Data` shape returned. Cross-reference this when writing scripts that parse audit output.
232+
- **[audit-workflow-guide.md](./references/audit-workflow-guide.md)** — Narrative playbook for the four canonical investigations: who-did-X, login-history, date-range-dump, and org-vs-tenant comparison. Each scenario has a worked example with literal flag values.

0 commit comments

Comments
 (0)