From 3d666be2ea591071222b09a6f500d8466e59e84c Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Fri, 1 May 2026 17:35:02 +0100 Subject: [PATCH 1/7] docs(uipath-maestro-flow): add IxP extraction plugin --- CODEOWNERS | 4 + skills/uipath-maestro-flow/SKILL.md | 2 +- .../references/author/CAPABILITY.md | 3 + .../author/references/planning-arch.md | 11 + .../author/references/planning-impl.md | 10 + .../author/references/plugins/ixp/impl.md | 376 ++++++++++++++++++ .../author/references/plugins/ixp/planning.md | 90 +++++ .../references/plugins/ixp/self-check.py | 91 +++++ .../single_node/ixp/activation.yaml | 114 ++++++ .../single_node/ixp/activation_listing.yaml | 75 ++++ .../single_node/ixp/activation_negative.yaml | 79 ++++ .../e2e_01_invoice_extraction_greenfield.yaml | 105 +++++ .../ixp/e2e_02_project_selection.yaml | 297 ++++++++++++++ .../ixp/integration_handle_routing.yaml | 122 ++++++ .../ixp/smoke_01_scaffold_minimal.yaml | 87 ++++ .../ixp/smoke_02_scaffold_multinode.yaml | 103 +++++ 16 files changed, 1568 insertions(+), 1 deletion(-) create mode 100644 skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md create mode 100644 skills/uipath-maestro-flow/references/author/references/plugins/ixp/planning.md create mode 100644 skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml create mode 100644 tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 271138832..ea25a4999 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -55,6 +55,10 @@ /skills/uipath-maestro-flow/references/plugins/connector/ @baishalighosh @chandusailella @bai-uipath @rockymadden /skills/uipath-maestro-flow/references/plugins/connector-trigger/ @baishalighosh @chandusailella @bai-uipath @rockymadden +# IXP +/skills/uipath-maestro-flow/references/author/references/plugins/ixp/ @jcalero @constantinmuraru @scallaway-uipath @tommilligan @joe-prosser @vlad-crisan @richsilv @AWilcke +/tests/tasks/uipath-maestro-flow/single_node/ixp/ @jcalero @constantinmuraru @scallaway-uipath @tommilligan @joe-prosser @vlad-crisan @richsilv @AWilcke + # Review skill /skills/uipath-review/ @AlvinStanescu @gabrielavaduva @roalexandru diff --git a/skills/uipath-maestro-flow/SKILL.md b/skills/uipath-maestro-flow/SKILL.md index 98e54f1f7..0cc15a17c 100644 --- a/skills/uipath-maestro-flow/SKILL.md +++ b/skills/uipath-maestro-flow/SKILL.md @@ -1,6 +1,6 @@ --- name: uipath-maestro-flow -description: "UiPath Maestro Flow (.flow) — build, edit, run, debug, fix. Create, connect nodes; connector, approval, script, subflow; triggers, schedules; validate. Upload, publish, manage runs, instances. Diagnose errors, incidents, traces. `uip maestro flow` CLI. For C#/XAML→uipath-rpa. For agents→uipath-agents." +description: "UiPath Maestro Flow (.flow) — build, edit, run, debug, fix. Create, connect nodes; connector, approval, script, subflow, ixp; triggers, schedules; validate. Upload, publish, manage runs, instances. Diagnose errors, incidents, traces. `uip maestro flow` CLI. For C#/XAML→uipath-rpa. For agents→uipath-agents." allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- diff --git a/skills/uipath-maestro-flow/references/author/CAPABILITY.md b/skills/uipath-maestro-flow/references/author/CAPABILITY.md index c23a7cb61..897e74455 100644 --- a/skills/uipath-maestro-flow/references/author/CAPABILITY.md +++ b/skills/uipath-maestro-flow/references/author/CAPABILITY.md @@ -63,6 +63,8 @@ Capability index for building new flows (greenfield) and editing existing flows | **Wire one node's output into another node's input** | [shared/node-output-wiring.md](../shared/node-output-wiring.md) | | **Orchestrate RPA, agents, apps** | Relevant resource plugin: [rpa](references/plugins/rpa/), [agent](references/plugins/agent/), [agentic-process](references/plugins/agentic-process/), [flow](references/plugins/flow/), [api-workflow](references/plugins/api-workflow/), [hitl](references/plugins/hitl/) | | **Embed an AI agent tightly coupled to this flow** | [plugins/inline-agent/](references/plugins/inline-agent/) | +| **Extract structured fields from documents** | [plugins/ixp/](references/plugins/ixp/) — IxP extraction models for PDFs, scanned forms, receipts, invoices, contracts | +| **List IxP models / runtime projects available in flow** | [plugins/ixp/impl.md — Listing Published Models](references/plugins/ixp/impl.md#listing-published-models) — read-only registry search, no `.flow` scaffold or edits | | **Create a resource that doesn't exist yet** | Use `core.logic.mock` placeholder — see [editing-operations-cli.md](references/editing-operations-cli.md#replace-a-mock-with-a-real-resource-node) + relevant plugin's `impl.md` | | **Add data transform nodes** | [plugins/transform/impl.md](references/plugins/transform/impl.md) | | **Create a subflow** | [plugins/subflow/impl.md](references/plugins/subflow/impl.md) + [Edit/Write: Create a subflow](references/editing-operations-json.md#create-a-subflow) | @@ -127,6 +129,7 @@ Capability index for building new flows (greenfield) and editing existing flows - [hitl](references/plugins/hitl/) — human input via UiPath Apps - [agent](references/plugins/agent/) — published AI agent resources - [inline-agent](references/plugins/inline-agent/) — autonomous agent embedded in flow + - [ixp](references/plugins/ixp/) — published IxP document-extraction models (PDFs, scanned forms, receipts, invoices, contracts) - [queue](references/plugins/queue/) — Orchestrator queue item creation ### Cross-capability (shared) diff --git a/skills/uipath-maestro-flow/references/author/references/planning-arch.md b/skills/uipath-maestro-flow/references/author/references/planning-arch.md index 3bd56a25a..3db748457 100644 --- a/skills/uipath-maestro-flow/references/author/references/planning-arch.md +++ b/skills/uipath-maestro-flow/references/author/references/planning-arch.md @@ -145,6 +145,9 @@ Resource nodes invoke published UiPath automations. They are tenant-specific and | Flow | `uipath.core.flow.{key}` | [flow](plugins/flow/planning.md) | | API Workflow | `uipath.core.api-workflow.{key}` | [api-workflow](plugins/api-workflow/planning.md) | | Human Task (app-based) | `uipath.core.human-task.{key}` | [hitl](plugins/hitl/planning.md) | +| Document Extraction | `uipath.ixp.{modelName}.{fullyQualifiedName}` | [ixp](plugins/ixp/planning.md) | + +> The IxP entry uses a **two-segment tail** (`{modelName}.{fullyQualifiedName}`), unlike the other resource nodes which use a single-segment `{key}` tail. Both segments are sanitized at registry-emit time. See [plugins/ixp/planning.md](plugins/ixp/planning.md) for the sanitization rule. ### Placeholders @@ -192,6 +195,7 @@ Use this when defining edges. Every edge requires a `sourcePort` and `targetPort | `uipath.core.flow.*` | `input` | `output`, `error` | | `uipath.core.agentic-process.*` | `input` | `output`, `error` | | `uipath.core.api-workflow.*` | `input` | `output`, `error` | +| `uipath.ixp.*` | `input` | `success`, `error` | | `uipath.connector.*` (activities) | `input` | `output`, `error` | | `core.action.queue.create` | `input` | `success` | | `core.action.queue.create-and-wait` | `input` | `success` | @@ -497,6 +501,13 @@ Quick decision guide. For full details, read the linked plugin's `planning.md`. - Agent is tightly coupled to this flow, not reused -> [inline-agent](plugins/inline-agent/planning.md) (`uipath.agent.autonomous`) - Agent is a published tenant resource, reused across flows -> [agent](plugins/agent/planning.md) (`uipath.core.agent.{key}`) +### "I need to extract structured fields from documents" + +- Source is a PDF, scanned form, photo, or email attachment with **variable layout** across inputs (invoices from many vendors, receipts, contracts, forms) -> [ixp](plugins/ixp/planning.md) (`uipath.ixp.{modelName}.{fullyQualifiedName}`) +- Source is already structured (CSV, JSON, database row) -> [script](plugins/script/planning.md) or [transform](plugins/transform/planning.md) +- Need free-form summarization, classification, or open-ended reasoning -> [agent](plugins/agent/planning.md) or [inline-agent](plugins/inline-agent/planning.md) +- IxP model not yet trained -> use `core.logic.mock` and surface in Open Questions + ### "The flow needs something outside flow capabilities" 1. Add a `core.logic.mock` placeholder diff --git a/skills/uipath-maestro-flow/references/author/references/planning-impl.md b/skills/uipath-maestro-flow/references/author/references/planning-impl.md index 32a913f6f..d5827afcd 100644 --- a/skills/uipath-maestro-flow/references/author/references/planning-impl.md +++ b/skills/uipath-maestro-flow/references/author/references/planning-impl.md @@ -57,6 +57,7 @@ uip maestro flow registry get --output json | `uipath.core.api-workflow.*` | [api-workflow/impl.md](plugins/api-workflow/impl.md) | | `uipath.core.hitl.*` | [hitl/impl.md](plugins/hitl/impl.md) | | `uipath.connector.uipath-uipath-dataservice.*` | [connector/data-fabric/impl.md](plugins/connector/data-fabric/impl.md) | +| `uipath.ixp.*` | [ixp/impl.md](plugins/ixp/impl.md) | | `uipath.connector.*` | [connector/impl.md](plugins/connector/impl.md) | | `uipath.connector.trigger.*` | [connector-trigger/impl.md](plugins/connector-trigger/impl.md) | @@ -101,6 +102,15 @@ uip maestro flow registry search "" --output json If found in neither, keep the `core.logic.mock` placeholder and note the gap. +#### IxP nodes — context-dispatched, no bindings + +IxP extraction nodes (`uipath.ixp.*`) skip binding resolution. Design-time configuration (`folderKey`, `modelName`) is emitted into the BPMN `model.context[]` array at build time (the serializer also pins `digitizationMode` to `"fileUpload"` internally), not into a separate `bindings_v2.json` file or a top-level `bindings[]` entry. Consequence for Phase 2: + +- No connection ID to bind — the node carries its tenant context inline. +- `inputs.*` is the source of truth for runtime values; validate against `registry get` `inputDefinition.properties` rather than against a binding schema. +- The node instance also carries a structured `inputs.model` blob (extraction-model metadata) that the property panel's `ixp-model-taxonomy` component reads. Copy `inputDefaults.model` verbatim — omitting it crashes the panel with `Cannot destructure property 'modelName' of 't' as it is undefined`. +- See [plugins/ixp/impl.md](plugins/ixp/impl.md) for the full JSON shape. + ### Step 4 — Replace Mock Nodes For each `core.logic.mock` node in the architectural plan: diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md new file mode 100644 index 000000000..5afc426c7 --- /dev/null +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -0,0 +1,376 @@ +# IxP Extraction Node — Implementation + +IxP Extraction nodes invoke a published UiPath Intelligent eXtraction Platform (IxP) model. They are tenant-specific resources with pattern `uipath.ixp.{sanitized-modelName}.{sanitized-fullyQualifiedName}`. + +Sanitization rule (applied to both tail segments, in this order): + +1. Lowercase. +2. Replace runs of any character outside `[a-z0-9]` with a single `-`. Slashes, spaces, underscores, and runs of dashes (e.g. `---`) all collapse to a single `-`. Dots in the FQN are NOT preserved — they also collapse to `-`. + +The dot in `uipath.ixp.{model}.{fqn}` is the segment separator the registry adds *after* sanitizing each tail segment, not part of the sanitization itself. + +Examples (verified against the live registry): + +- `"birth_certificates_oob-6252526a-ixp"` + FQN `Shared/birth_certificates_oob-6252526a-ixp` → `uipath.ixp.birth-certificates-oob-6252526a-ixp.shared-birth-certificates-oob-6252526a-ixp` (underscores and slash both → `-`). +- `"idp-benchmark---invoices-c735405a-ixp"` + FQN `Shared/idp-benchmark---invoices-c735405a-ixp` → `uipath.ixp.idp-benchmark-invoices-c735405a-ixp.shared-idp-benchmark-invoices-c735405a-ixp` (run of `---` → single `-`). + +Always prefer the `nodeType` returned by `uip maestro flow registry search` over constructing one by hand. + +## Discovery + +```bash +uip maestro flow registry pull --force +uip maestro flow registry search "uipath.ixp" --output json +``` + +Requires `uip login`. Only published IxP models from your tenant appear. Example returned `nodeType`: `uipath.ixp.idp-benchmark-invoices-c735405a-ixp.shared-idp-benchmark-invoices-c735405a-ixp` (for an `idp-benchmark---invoices-c735405a-ixp` model in the `Shared` folder). + +### Response shape + +`registry search` returns a top-level envelope; `Data` is a flat list of node entries (PascalCase keys): + +```json +{ + "Result": "Success", + "Code": "NodeSearchSuccess", + "Data": [ + { + "NodeType": "uipath.ixp.idp-benchmark-invoices-c735405a-ixp.shared-idp-benchmark-invoices-c735405a-ixp", + "Category": "document-processing", + "DisplayName": "idp-benchmark---invoices-c735405a-ixp", + "Description": "(Shared)", + "Version": "1.0.0", + "Tags": "ixp, document-understanding, extraction" + } + ] +} +``` + +Read entries as `raw["Data"][i]["NodeType"]` (not `raw["Data"]["Nodes"]`). + +### If `Data` is empty → stop and use a mock + +If `uip maestro flow registry search "uipath.ixp"` returns `Data: []`, **no IxP extraction model is published on this tenant**. Add a `core.logic.mock` placeholder node (see [If the Model Does Not Exist Yet](#if-the-model-does-not-exist-yet)) and surface the missing model in **Open Questions**. + +**Stop searching.** Do not run any of these as a fallback: + +- Domain-keyword searches: `registry search "invoice"`, `"form"`, `"document"`, `"W-9"`, `"receipt"`, `"contract"`, etc. — there is no domain-named extraction node; IxP is the only extraction primitive. +- `registry list` followed by client-side filtering for "ixp" / "extraction" — the strict `uipath.ixp` search is already authoritative. +- Variant-prefix searches: `registry search "uipath.agent.resource.tool.ixp"`, `"core.ixp"`, etc. + +The fallback is `core.logic.mock`, full stop. At most run one broader `registry search "ixp"` to confirm there are no `uipath.ixp.*` hits hidden by stricter prefix matching, then mock. + +> A `uipath.agent.resource.tool.ixp.*` hit on the broader `"ixp"` search is the *agent-tool* variant — not a flow extraction node. Treat it as "no extraction model published" and fall back to mock. + +## Listing Published Models + +When the user asks what IxP models are available — equivalently, "what runtime projects do I have access to in flow", "what document extractors can I access in flow", "list published extractors" — answer with the same registry search. Each `Data[]` entry corresponds to one published model (a.k.a. runtime project) on the tenant. + +```bash +uip login status --output json # confirm auth — without login, tenant IxP nodes are hidden +uip maestro flow registry pull --force +uip maestro flow registry search "uipath.ixp" --output json +``` + +Parse `Data[].DisplayName`, `Data[].NodeType`, and `Data[].Version` and present them as a table. Example: + +| Model (DisplayName) | NodeType | Version | +| --- | --- | --- | +| idp-benchmark---invoices-c735405a-ixp | `uipath.ixp.idp-benchmark-invoices-c735405a-ixp.shared-idp-benchmark-invoices-c735405a-ixp` | `1.0.0` | + +Rules for the listing path: + +- **Do NOT scaffold a solution, run `uip maestro flow init`, or write a `.flow` file.** Listing is read-only Q&A. +- **Do NOT mock.** If `Data: []`, answer directly: no IxP models are published on this tenant. The `core.logic.mock` fallback is for build-time planning, not for listing-time Q&A. +- **Do NOT log in for the user.** If `uip login status` shows logged-out, tell the user to run `uip login` and stop — listing without auth returns OOTB-only results and is misleading. +- **Do NOT search by `"runtime"`, `"document extractor"`, `"extractor"`, or `"IXP"` (uppercase).** These return empty results or agent-tool variants — not extraction nodes. Use `"uipath.ixp"` (lowercase) only. +- **Do NOT use `uip maestro flow process list` or any Orchestrator folder iteration.** `flow process list` enumerates *deployed flow process instances* (with `--folder-key`) — that is a different question. Listing published IxP models always goes through `registry search "uipath.ixp"`. +- **Do NOT guess `uip maestro flow list-*` or `uip maestro ixp list-*` subcommands.** None exist. The CLI returns `unknown command 'list-...'` and there is no fallback path to pursue. + +## Registry Validation + +```bash +uip maestro flow registry get "" --output json +``` + +Confirm: + +- `category` — `document-processing` (older `document-extraction` enum was renamed; current registry serves `document-processing`) +- Input port: `input` +- Output ports: `success` and `error` (the `error` port is gated by `inputs.errorHandlingEnabled`; manifest sets `supportsErrorHandling: true`). Edges target these handle ids in `.flow` JSON; `handleType` is `output`. +- `model.type` — `bpmn:ServiceTask`. `model.serviceType` — `IXP.Extraction`. The manifest's `model` is two fields only (`type`, `serviceType`) — no `context`, no `version`. Both are injected by the BPMN serializer at compile time. +- `form.id` — `ixp-standalone-form`. Three sections: `ixp-model` (Configuration), `ixp-file-upload` (File input), `schema-definition` (Schema definition — a single custom field `inputs.model` rendered by the `ixp-model-taxonomy` component). +- `inputDefinition.properties` — `model` (object), `modelName`, `projectName`, `projectId`, `versionTag`, `folderKey`, `folderName`, `fileRef`, `pageRange`, `attachmentConfig`, `guardrails`, `attachment`. `inputDefinition.required` — `["fileRef"]`. +- `inputDefaults` — carries the full `model` metadata blob plus flat `modelName` / `projectName` / `folderKey` / `folderName` mirrors. The blob shape is `{ modelName, fullyQualifiedName, description, folderKey, folderName }` plus DU-API extras (`kind`, `type`, `detailsUrl`, `asyncDigitizationUrl`, `asyncExtractionUrl`). +- `outputDefinition` — populated. `output` carries the full extraction-result JSON schema; `error` carries the standard error envelope. + +## Adding / Editing + +For step-by-step add, delete, and wiring procedures, see [editing-operations.md](../../editing-operations.md). Use the JSON structure below for the node-specific `inputs` and `outputs` fields. Author CAPABILITY rule #15 (no top-level `model` block on the instance) and rule #14 (`outputs` populated for nodes that produce data) both apply. + +## JSON Structure + +The IxP node instance carries `inputs` and `outputs` — and **no top-level `model` block**. The slim manifest `model` (`{ type, serviceType }`) lives only in `definitions[]`; the runtime `model.context` / `model.version` / `model.inputs` / `model.outputs` envelope is injected by the BPMN serializer at compile time. + +### Build procedure — copy from `registry get`, do not construct from memory + +The IxP node instance is **derived from the registry response**, not authored from scratch. Any IxP node built from training-data recall will hit at least one of: missing `inputs.model` (canvas crash), missing `outputs.error` (broken `$vars` resolution), legacy forbidden fields (silent schema drift). + +Run this once and source every field below from the response: + +```bash +uip maestro flow registry get "" --output json > /tmp/ixp-def.json +``` + +Then assemble the instance by copying these paths verbatim: + +| Instance field | Source path in `registry get` response | Required | +| --- | --- | --- | +| `inputs.model` (full object) | `Data.Node.inputDefaults.model` | **YES** — undefined → canvas crash | +| `inputs.modelName` | `Data.Node.inputDefaults.modelName` | YES | +| `inputs.projectName` | `Data.Node.inputDefaults.projectName` | YES | +| `inputs.folderKey` | `Data.Node.inputDefaults.folderKey` | YES | +| `inputs.folderName` | `Data.Node.inputDefaults.folderName` | YES | +| `inputs.versionTag` | `""` (empty string unless pinning a version) | YES | +| `inputs.pageRange` | `""` (empty string for full document) | YES | +| `inputs.fileRef` | `"=js:$vars..output."` (author this) | YES | +| `outputs.output` (full object) | `Data.Node.outputDefinition.output` | **YES** — missing → `$vars..output` fails | +| `outputs.error` (full object) | `Data.Node.outputDefinition.error` | **YES** — missing → `$vars..error` fails | + +**Forbidden in `inputs`** (legacy schema, removed from current standalone node — including any of these is a defect even if `flow validate` passes): + +- `digitizationMode` — serializer defaults to `fileUpload` internally +- `documentTaxonomy` — replaced by `inputs.model` blob +- `attachmentId` — use `inputs.attachment` for Orchestrator job attachments instead +- `fileName` — derived from `fileRef` upstream +- `mimeType` — derived from `fileRef` upstream + +If you find yourself typing any of those five field names while authoring an IxP node, stop and re-read this section. + +### Final shape + +```json +{ + "id": "extractInvoiceFields", + "type": "uipath.ixp.invoice-model.shared-invoice-model", + "typeVersion": "1.0.0", + "display": { "label": "Extract Invoice Fields" }, + "inputs": { + "model": { + "modelName": "Invoice Model", + "description": "", + "kind": "Extractor", + "type": "IXP", + "fullyQualifiedName": "Shared/invoice-model", + "detailsUrl": "https://.uipath.com/.../models/invoice-model?api-version=2.0", + "asyncDigitizationUrl": "https://.uipath.com/.../models/invoice-model/digitization/start?api-version=2.0", + "asyncExtractionUrl": "https://.uipath.com/.../models/invoice-model/extraction/start?api-version=2.0", + "folderKey": "", + "folderName": "Shared" + }, + "modelName": "Invoice Model", + "description": "", + "projectName": "Invoice Model", + "versionTag": "", + "folderKey": "", + "folderName": "Shared", + "fileRef": "=js:$vars.start.output.invoice", + "pageRange": "" + }, + "outputs": { + "output": { /* copy verbatim from definition.outputDefinition.output */ }, + "error": { /* copy verbatim from definition.outputDefinition.error */ } + } +} +``` + +### Authoring rules + +1. **`inputs.model` MUST be present and MUST be the full blob from `Data.Node.inputDefaults.model`.** Copy verbatim — do not synthesize, do not abbreviate, do not omit the DU-API extras (`detailsUrl`, `asyncDigitizationUrl`, `asyncExtractionUrl`, `kind`, `type`). The `schema-definition` form section binds `inputs.model` to the `ixp-model-taxonomy` custom component, which destructures `modelName` and `folderKey` out of it. If `inputs.model` is undefined, clicking the node in Studio Web crashes the property panel with `Cannot destructure property 'modelName' of 't' as it is undefined` — and `flow validate` does not catch it. +2. **Flat mirrors stay alongside `inputs.model`.** `modelName`, `projectName`, `folderKey`, `folderName` are surfaced as disabled text fields in the `ixp-model` form section and are read directly from `inputs.*`, not from `inputs.model.*`. +3. **`fileRef` is the only schema-required input** (`inputDefinition.required: ["fileRef"]`). Use `=js:$vars..output.` per Critical Rule #13. +4. **`outputs.output` AND `outputs.error` MUST both be present**, copied verbatim from `Data.Node.outputDefinition.output` and `Data.Node.outputDefinition.error`. Omitting either breaks downstream `$vars..output` / `.error` resolution and hides the field in Studio Web's variable picker. `flow validate` does not catch the omission. +5. **No top-level `model` on the instance.** Studio Web–authored .flow files never carry one; the BPMN-format `model` envelope (with `context`, `version`, `inputs`, `outputs`) is emitted at serialize time only. +6. **`inputs` MUST NOT contain `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, or `mimeType`.** These five fields were on a prior schema and have been removed from the standalone IxP node. Including them is the most common training-data-recall mistake. The serializer defaults `digitizationMode` to `fileUpload` internally — there is no scenario where you should set it on the instance. + +The `definitions[]` entry is copied verbatim from `registry get` (`Data.Node`). Critical Rule #7 applies unchanged. + +### Self-check before `uip maestro flow validate` + +`uip maestro flow validate` only checks structural conformance to the node manifest, and the IxP manifest declares `inputDefinition.required: ["fileRef"]` — so a flow with `inputs.model` undefined, missing `outputs.error`, or legacy fields in `inputs` passes `validate` cleanly and then crashes Studio Web's property panel at click time (`Cannot destructure property 'modelName' of 't' as it is undefined`). Run this self-check on the `.flow` file you wrote, **before** `flow validate`. + +> **DO NOT MODIFY THIS SCRIPT.** Run it as-is. Do NOT add suppressions, downgrade errors to warnings, remove `sys.exit(1)`, or rephrase the failure messages. The heredoc below is a flattened, function-free copy of [`self-check.py`](self-check.py) in this directory — the two forms are not byte-equivalent (the `.py` wraps the logic in `check_flow()` / `main()`, the heredoc inlines it) but they enforce the same rules with the same exit codes. CI runs the same logic against the generated `.flow`. A modified self-check that exits 0 is treated as a failed run, not a pass. +> +> The Authoring rules above are the source of truth for the instance contract. The registry's `inputDefinition.properties` is **not** a license to override them: it is the schema of the property catalog (current keys only — `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, `mimeType` are NOT returned by `registry get`). If a forbidden field appears in your `inputs`, you put it there from training-data recall, not from the registry response. + +```bash +python3 - "//.flow" <<'PY' +import json, sys +FORBIDDEN_INPUT_FIELDS = {"digitizationMode", "documentTaxonomy", "attachmentId", "fileName", "mimeType"} +with open(sys.argv[1]) as fh: + flow = json.load(fh) +errors = [] +for node in flow.get("nodes") or []: + if not isinstance(node, dict) or not str(node.get("type", "")).startswith("uipath.ixp."): + continue + nid = node.get("id", "?") + inputs = node.get("inputs") or {} + outputs = node.get("outputs") or {} + model = inputs.get("model") + if not isinstance(model, dict) or not model.get("modelName") or not model.get("folderKey"): + errors.append(f"{nid}: rule #1 — inputs.model.{{modelName,folderKey}} must be present (canvas crashes with 'Cannot destructure property modelName' otherwise)") + fileRef = inputs.get("fileRef", "") + if not isinstance(fileRef, str) or not fileRef.startswith("=js:$vars."): + errors.append(f"{nid}: rule #3 — inputs.fileRef must be '=js:$vars..output.'") + for port in ("output", "error"): + if port not in outputs: + errors.append(f"{nid}: rule #4 — outputs.{port} required (downstream $vars.{nid}.{port} won't resolve)") + legacy = sorted(FORBIDDEN_INPUT_FIELDS & set(inputs.keys())) + if legacy: + errors.append(f"{nid}: rule #6 — forbidden legacy fields in inputs: {legacy} (removed from current schema; registry-get does NOT return these)") + if node.get("model") is not None: + errors.append(f"{nid}: rule #5 — top-level 'model' on instance (must live in definitions[])") +if errors: + print("IxP self-check FAILED", file=sys.stderr) + for e in errors: + print(f" {e}", file=sys.stderr) + sys.exit(1) +print("IxP self-check passed.") +PY +``` + +Procedure: + +1. **Exit 0** → proceed to `uip maestro flow validate .flow --output json`. +2. **Exit 1** → read each error line; the rule number tells you which Authoring rule above to re-read. Fix the **`.flow` file**, not the script. The fix is almost always one of: copy `inputs.model` verbatim from the `registry get` response you already fetched, copy `outputs.{output,error}` verbatim from `outputDefinition`, or delete a legacy field from `inputs`. Re-run the self-check. +3. **Maximum 2 fix attempts.** If the third self-check still fails, stop and surface the remaining errors to the user — do not declare the flow done, do not run `flow validate` to paper over the failure, and do not edit the script to make it pass. + +If you find yourself reading the script and considering a "this rule doesn't apply because the registry says..." rationalization: stop. The registry response is in your transcript; grep it for the field name. If it isn't there (and for the five forbidden fields it never is), the rule applies. Run the script unmodified. + +### `inputs.fileRef` vs the emitted `model.inputs[]` body + +`inputs.fileRef` is the source of truth. At BPMN serialize time, `packages/services/src/serialization/uipath-extension.ts:handleIxpExtraction` wraps the value into a `model.inputs[]` entry with target `bodyField` and body `{"downloadedFileOutput": }`. Edit `inputs.fileRef` only; never hand-edit the BPMN body. + +### Optional `attachment` input (Orchestrator job attachments) + +`inputDefinition.properties.attachment` accepts `{ ID, FullName, MimeType, Metadata }` for flows that consume Orchestrator job attachments. There is no form UI for this path on the standalone node today — set it programmatically in `inputs.attachment` if needed. `ID` is the only required field. Validate end-to-end on your tenant before relying on this path. + +## Accessing Output + +The extraction result is stored at `$vars.{nodeId}.output`. The IxP node's BPMN serializer maps the extraction service's `result` field directly to this variable (`source: '=result'`), so **the `result` wrapper is stripped** — `output` IS the extraction-result object, with no further wrapping. + +Top-level keys of `$vars.{nodeId}.output`: + +- `ExtractionResult` — `{ DocumentId, ResultsVersion, ResultsDocument }`. `ResultsDocument.Fields[]` carries the trained model's extracted values; `ResultsDocument.Tables[]` carries tabular extractions. +- `ExtractorPayloads` — provider-specific raw payloads. +- `BusinessrulesResults[]` — business-rule evaluation results, when configured. + +Each `Fields[]` element is shaped: + +```json +{ + "FieldId": "string", + "FieldName": "string", + "FieldType": "string", + "IsMissing": false, + "Values": ["string"], + "Confidence": 95 +} +``` + +Read field values via `find` against `FieldName`, then index into `Values[]`: + +```javascript +// In a Script node after the IxP node +const fields = $vars.extractInvoiceFields.output.ExtractionResult.ResultsDocument.Fields || []; +const total = fields.find(f => f.FieldName === 'invoiceTotal')?.Values?.[0]; +const vendor = fields.find(f => f.FieldName === 'vendor')?.Values?.[0]; +return { total, vendor }; +``` + +Sibling error variable: `$vars.{nodeId}.error` — populated when extraction fails *and* the `error` port is wired (`supportsErrorHandling: true`). Mapped from the service response's `Error` field (`source: '=Error'`). + +### Wrong shapes the agent tends to invent + +These all pass `flow validate` and fail silently at runtime: + +- **Wrong:** `output.result.ExtractionResult.…` — there is no `result` wrapper at runtime; `=result` strips it before the value is assigned to `output`. +- **Wrong:** `output.` flat — extracted fields are not top-level properties of `output`; they live under `output.ExtractionResult.ResultsDocument.Fields[]` and are keyed by `FieldName`. +- **Wrong:** `output.ExtractionResult.Fields` — `Fields[]` is two levels under `ExtractionResult` (`output.ExtractionResult.ResultsDocument.Fields`), not one. + +Studio Web's variable picker renders `output.ExtractionResult` as opaque and does NOT surface the nested `ResultsDocument.Fields[]` shape. The path above is the source of truth — copy it from this doc, not from picker autocomplete or `outputDefinition.output.schema` (the registry schema describes the pre-`=result` wrapper, not the runtime variable). + +### Trained-model field taxonomy + +The `FieldName` values present in `ResultsDocument.Fields[]` depend on the trained IxP model's taxonomy and are NOT exposed through `uip maestro flow registry get` (the registry's `outputDefinition.output.schema` describes the wrapper envelope shape, not the per-model trained fields). Get them from the deployment: + +```bash +uip ixp deployments get-taxonomy --folder-key "" --output json +``` + +Both args come from the `registry get` response you already fetched in [Build procedure](#build-procedure--copy-from-registry-get-do-not-construct-from-memory): `--folder-key` ← `Data.Node.inputDefaults.folderKey`; `` (positional) ← `Data.Node.inputDefaults.modelName`. No additional discovery step. Requires `uip login`; the command uses the user Bearer to call the same DU-App route that Studio Web's "Schema definition" panel uses. + +Response shape: + +```json +{ + "documentTaxonomy": { + "documentTypes": [ + { + "fields": [ + { + "fieldId": "string", + "fieldName": "string", + "type": "Text", + "components": [] + } + ] + } + ] + } +} +``` + +`type` is one of `Text`, `Date`, `Number`, `Set`, `FieldGroup`. `components[]` is populated only when `type` is `FieldGroup` and carries sub-fields with the same shape recursively. + +**camelCase → PascalCase translation.** The taxonomy response uses `fieldName` (camelCase); runtime `Fields[]` elements use `FieldName` (PascalCase). The string *contents* match — design-time `"Birth Date"` is `FieldName: "Birth Date"` at runtime — but the wrapper key changes case. Translate the key, not the value, when going from `get-taxonomy` output to runtime `Fields[].FieldName` lookups. + +Agent call sequence: + +1. `uip maestro flow registry search "uipath.ixp" --output json` — list IxP nodes. +2. `uip maestro flow registry get "" --output json` — read `Data.Node.inputDefaults.{folderKey, modelName}` (already done as part of [Build procedure](#build-procedure--copy-from-registry-get-do-not-construct-from-memory)). +3. `uip ixp deployments get-taxonomy --folder-key "" --output json` — read `documentTaxonomy.documentTypes[].fields[].fieldName`. +4. Author downstream consumers with `$vars..output.ExtractionResult.ResultsDocument.Fields.find(f => f.FieldName === '')?.Values?.[0]`. + +If the command fails (login expired, deployment not yet published, transient failure), fall back to defensive `find`-by-`FieldName` patterns with assumed field names and surface the assumptions to the user under **Open Questions**. Do NOT substitute a one-off extraction or IxP-product-UI inspection in the agent loop — `get-taxonomy` is the agent-loop path. + +## If the Model Does Not Exist Yet + +Trigger: `uip maestro flow registry search "uipath.ixp"` returns `Data: []`, OR the only matches are `uipath.agent.resource.tool.ixp.*` (agent-tool variant — not a flow extraction node). + +Action: insert a `core.logic.mock` placeholder via Direct JSON edit and stop. Do not iterate on registry searches. + +1. Fetch the definition: `uip maestro flow registry get core.logic.mock --output json`. Copy `Data.Node` verbatim into `definitions[]` if not already present. +2. Add a node to `nodes[]` with a stable id (e.g. `extractContractFieldsMock`), `type: "core.logic.mock"`, and a `display.label` whose **leading phrase** describes the work in the user's domain (e.g. `Extract Contract Fields`) rather than the underlying technology (`IxP Extraction`, `Run IxP`). The parenthetical may name IxP — e.g. `Extract Contract Fields (mock — IxP model not yet published)`. +3. Add a `layout.nodes` entry at `position: { x: 400, y: 144 }`, size `96x96`. +4. Wire edges per the parent [editing-operations.md](../../editing-operations.md) guide. `core.logic.mock` is a no-op pass-through — no `inputs`, no `outputs` block, no `bindings_v2.json` changes. +5. **Wire downstream consumers against the mock with `$vars` references, not static values.** Scripts, decisions, and end-node mappings that follow the mock MUST reference `$vars.{mockNodeId}.output` (the mock's only port) instead of hard-coded returns. Example: a script that summarises the (future) extraction writes `return { vendor: $vars.extractInvoiceFieldsMock.output.vendorName };`, not `return { ok: "OK" };`. This keeps the **node-graph** swap-ready — node IDs, edge shapes, and the `output` port name stay intact when the mock is replaced. **Field-access paths inside downstream scripts WILL need rewriting at swap time** — the real IxP `output` is shaped as `{ ExtractionResult: { ResultsDocument: { Fields: [...] } } }` (see [Accessing Output](#accessing-output)), so flat-field accessors against the mock become structured `Fields.find(f => f.FieldName === '')?.Values?.[0]` lookups against the real node. Surface the post-swap rewrite as a follow-up under **Open Questions**. +6. Run `uip maestro flow validate .flow --output json` once after all edits complete. + +Surface the missing model in the **Open Questions** section of the architectural plan: the user must train and publish the IxP extraction model via the IxP product before the flow can run. After publishing, follow the [mock replacement procedure](../../editing-operations-json.md#replace-a-mock-with-a-real-resource-node) to swap the mock for the real IxP node. + +## Classifier Variant + +IxP also exposes classifier models (type `Classifier`) that label documents rather than extracting named fields. Classifier models share the `uipath.ixp.*` node-type pattern but produce a different `output` shape (classification labels, not field values). **Classifier configuration is not covered in this file** — if the user needs classification, flag it as a prerequisite and defer to a future revision of this impl.md. + +## Debug + +| Error | Cause | Fix | +| --- | --- | --- | +| Node type not found in registry | Model not published, or registry cache stale | Run `uip login` then `uip maestro flow registry pull --force` | +| `model.context` rejected by runtime | `folderKey` or `modelName` missing from `inputs` (the context array is built from these) | Confirm `inputs.modelName` and `inputs.folderKey` are populated. | +| Empty `$vars.{nodeId}.output` | Model's taxonomy doesn't match the document, or extraction silently returned no fields | Inspect the raw API response via `$vars.{nodeId}.error` first; if no error, run the extraction against the same document on the IxP product UI to compare | +| `fileRef` not resolving | Expression references an upstream variable that isn't wired, or the upstream node didn't produce a file output | Verify the upstream node exports a file reference and that the `=js:$vars.{upstreamId}.output.` expression matches | +| Extraction failed | Underlying IxP model errored (unsupported MIME type, corrupted file, service-side failure) | Check `$vars.{nodeId}.error.detail` for the IxP service response | +| `uip maestro flow node configure` rejects with "not a connector type node" | Expected — IxP is not a connector. | Edit `inputs.*` in the `.flow` JSON directly. | +| Studio Web: "Cannot destructure property 'modelName' of 't' as it is undefined" when clicking the node | `inputs.model` blob is missing or undefined. The `schema-definition` form section binds `inputs.model` to the `ixp-model-taxonomy` component, which destructures `modelName` and `folderKey` out of it. When `inputs.model` is missing, the destructure throws. | Copy `definition.inputDefaults.model` verbatim into the node instance's `inputs.model`. The blob carries `modelName`, `fullyQualifiedName`, `description`, `folderKey`, `folderName` plus DU-API extras (`kind`, `type`, `detailsUrl`, `asyncDigitizationUrl`, `asyncExtractionUrl`). See [JSON Structure](#json-structure). | diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/planning.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/planning.md new file mode 100644 index 000000000..33344da8a --- /dev/null +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/planning.md @@ -0,0 +1,90 @@ +# IxP Extraction Node — Planning + +IxP Extraction nodes invoke a **trained, published** UiPath Intelligent eXtraction Platform (IxP) model to pull structured fields out of unstructured or semi-structured documents (PDFs, photos, scanned forms). They are tenant-specific resources that appear in the registry after `uip login` + `uip maestro flow registry pull`. + +In the IxP product these published models are also called **runtime projects** — the bundle published to the tenant runtime and callable from a flow. Treat "what IxP models do I have", "what runtime projects do I have access to in flow", and "list published extractors" as the same listing question — see [Listing Available Models / Runtime Projects](#listing-available-models--runtime-projects). + +## Node Type Pattern + +`uipath.ixp.{modelName}.{fullyQualifiedName}` + +Unlike other resource plugins that use the `uipath.core.*` prefix, IxP nodes use `uipath.ixp.*` and a **two-segment tail**: `{modelName}` is the model's display name, and `{fullyQualifiedName}` is the model's fully-qualified name on the tenant (e.g. `Shared/invoice-model`). Both segments are sanitized — see [impl.md](impl.md) for the rule and concrete examples. Model versioning is a separate input field (`versionTag`), not part of the node type. + +## When to Use + +Use an IxP node when the flow needs to extract **named fields** (invoice totals, contract parties, receipt line items, form values) from documents whose **layout varies across inputs** and which are not already machine-readable. + +### IxP vs Script/Connector/HTTP/Agent Decision Table + +| Use an IxP node when... | Use something else when... | +| --- | --- | +| Source is a PDF, scanned document, photo, or email attachment | Source is already structured (CSV, JSON, database row) — use [Script](../script/planning.md) | +| Fields have variable layout across documents (e.g. invoices from many vendors) | Layout is fixed and parseable by regex/XPath — use [Script](../script/planning.md) | +| A trained IxP model exists for this document type | No model exists — flag as a prerequisite and use `core.logic.mock` until the model is trained | +| Need field-level extraction with per-field confidence scores for downstream HITL review | Need free-form summarization, classification, or reasoning over text — use [Agent](../agent/planning.md) | + +### Anti-Pattern + +Don't use IxP as a generic OCR. IxP is field-oriented — it extracts named fields against a trained taxonomy. Running IxP to get raw text back and then parsing the text downstream throws away the part of IxP that's actually valuable. + +### When NOT to Use + +- **Model not yet trained or published** — use `core.logic.mock` and surface in Open Questions that a model must be trained on the IxP product before the flow can run. +- **Structured input (CSV/JSON/DB row)** — use [Script](../script/planning.md) or [Decision](../decision/planning.md). IxP adds latency and model-inference cost for no gain. +- **Free-form summarization, classification, or reasoning** — use [Agent](../agent/planning.md). An IxP model trained for field extraction will not produce useful output for those tasks. +- **Manual external API call** — use [Connector](../connector/planning.md) or [HTTP](../http/planning.md). + +## Prerequisites + +- `uip login` — IxP nodes only appear in the registry after authentication. +- `uip maestro flow registry pull --force` must be run to cache IxP model node types locally. +- A trained, published IxP extraction model must exist on the tenant. If none exists, surface it in the **Open Questions** section of the architectural plan so the user can train one while reviewing. + +## Ports + +| Input Port | Output Port(s) | +| --- | --- | +| `input` | `success`, `error` (the visible `error` port / edge target is gated by `inputs.errorHandlingEnabled`; the `outputs.error` schema entry on the node instance is always required — see [impl.md Authoring rule #4](impl.md#authoring-rules)) | + +## Output Variables + +- `$vars.{nodeId}.output` — the extraction-result object directly. The BPMN serializer maps the service's `result` field with `source: '=result'`, so **the `result` wrapper is stripped** at runtime. Top-level keys: `ExtractionResult`, `ExtractorPayloads`, `BusinessrulesResults`. Field-level values live under `output.ExtractionResult.ResultsDocument.Fields[]` / `Tables[]`; exact field names depend on the trained model's taxonomy. See [impl.md — Accessing Output](impl.md#accessing-output) for the canonical access path. +- `$vars.{nodeId}.error` — populated when the `error` port is wired and extraction fails. Mapped from the IxP service response's `Error` field (`source: '=Error'`) — see [impl.md — Accessing Output](impl.md#accessing-output). + +## Discovery + +```bash +uip maestro flow registry pull --force +uip maestro flow registry search "uipath.ixp" --output json +``` + +Requires `uip login`. Only published IxP models from your tenant appear. The returned node type uses a **two-segment tail** (`{modelName}.{fullyQualifiedName}`), unlike `uipath.core.*` siblings which use a single-segment tail. Both tail segments are sanitized: lowercase, then runs of any character outside `[a-z0-9]` → single `-`. So an FQN of `Shared/invoice-model` lands as `shared-invoice-model`. See [impl.md](impl.md) for the full rule and worked examples. + +### If `Data: []` → plan for a mock + Open Question + +If the search returns no `uipath.ixp.*` nodes, no IxP extraction model is published on this tenant. Plan the architecture around a `core.logic.mock` placeholder for the extraction step, and surface the missing model in **Open Questions** so the user can train and publish it. Do not iterate on registry searches — the mock is the planning answer until the model exists. See [impl.md — If the Model Does Not Exist Yet](impl.md#if-the-model-does-not-exist-yet) for the implementer-side procedure. + +## Listing Available Models / Runtime Projects + +Q&A use case: user asks "what IxP models do I have?", "what runtime projects do I have access to in flow?", "what document extractors can I access in flow?", "which document extractors are published?". + +Read-only listing — **do not** scaffold a solution, init a flow, or write a `.flow` file: + +1. Confirm `uip login status --output json`. Without login, only OOTB nodes are returned and tenant IxP models will not appear. +2. Refresh the cache and search: + ```bash + uip maestro flow registry pull --force + uip maestro flow registry search "uipath.ixp" --output json + ``` +3. Format `Data[]` as a table — `DisplayName`, `NodeType`, `Version`. Each entry is one published model / runtime project. See [impl.md — Listing Published Models](impl.md#listing-published-models) for parsing details. + +Search term must be `"uipath.ixp"` (lowercase). Do NOT use `"runtime"`, `"document extractor"`, `"extractor"`, or `"IXP"` — these miss extraction nodes. + +If `Data: []`, answer directly that no IxP models are published on the tenant. Do not propose a `core.logic.mock` workaround — the mock is for build-time planning, not for listing-time Q&A. + +## Planning Annotation + +In the architectural plan: + +- If the model exists: note as `resource: (ixp-extraction)` with the intended document type (e.g. "resource: vendor-invoices (ixp-extraction) — extract invoice header + line items") +- If it does not exist: note as `[CREATE NEW] ` and flag in Open Questions that an IxP model must be trained before the flow can run diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py new file mode 100644 index 000000000..e17965be3 --- /dev/null +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +""" +IxP node self-check — canonical source. + +Enforces Authoring rules #1, #3, #4, #5, #6 from impl.md. Run on the +generated `.flow` file BEFORE `uip maestro flow validate`. Exits 0 when +the file is clean, 1 when any rule is violated. + +DO NOT MODIFY THIS SCRIPT TO SUPPRESS A FAILURE. The rules encode +runtime invariants (Studio Web canvas destructure, `$vars` resolution, +schema drift) that `flow validate` cannot catch. A failure here means +the `.flow` file is broken — fix the flow, not the check. + +The inline heredoc form embedded in impl.md ("Self-check before +`uip maestro flow validate`") is a flattened, function-free version of +the same checks. The two forms are NOT byte-equivalent — this file +wraps the logic in `check_flow()` / `main()`, the heredoc inlines it — +but they MUST stay logically equivalent: the same FORBIDDEN_INPUT_FIELDS +set, the same per-node assertions, the same rule numbers, the same exit +codes. When changing one, change the other. +""" +from __future__ import annotations + +import json +import sys + + +FORBIDDEN_INPUT_FIELDS = { + "digitizationMode", + "documentTaxonomy", + "attachmentId", + "fileName", + "mimeType", +} + + +def check_flow(path: str) -> list[str]: + with open(path) as fh: + flow = json.load(fh) + errors: list[str] = [] + for node in flow.get("nodes") or []: + if not isinstance(node, dict): + continue + if not str(node.get("type", "")).startswith("uipath.ixp."): + continue + nid = node.get("id", "?") + inputs = node.get("inputs") or {} + outputs = node.get("outputs") or {} + model = inputs.get("model") + if not isinstance(model, dict) or not model.get("modelName") or not model.get("folderKey"): + errors.append( + f"{nid}: rule #1 — inputs.model.{{modelName,folderKey}} must be present " + f"(canvas crashes with 'Cannot destructure property modelName' otherwise)" + ) + fileRef = inputs.get("fileRef", "") + if not isinstance(fileRef, str) or not fileRef.startswith("=js:$vars."): + errors.append( + f"{nid}: rule #3 — inputs.fileRef must be '=js:$vars..output.'" + ) + for port in ("output", "error"): + if port not in outputs: + errors.append( + f"{nid}: rule #4 — outputs.{port} required (downstream $vars.{nid}.{port} won't resolve)" + ) + legacy = sorted(FORBIDDEN_INPUT_FIELDS & set(inputs.keys())) + if legacy: + errors.append( + f"{nid}: rule #6 — forbidden legacy fields in inputs: {legacy} " + f"(removed from current schema; registry-get does NOT return these)" + ) + if node.get("model") is not None: + errors.append(f"{nid}: rule #5 — top-level 'model' on instance (must live in definitions[])") + return errors + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: self-check.py .flow", file=sys.stderr) + return 2 + errors = check_flow(sys.argv[1]) + if errors: + print("IxP self-check FAILED", file=sys.stderr) + for e in errors: + print(f" {e}", file=sys.stderr) + return 1 + print("IxP self-check passed.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml new file mode 100644 index 000000000..b66fa41fa --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml @@ -0,0 +1,114 @@ +task_id: skill-flow-ixp-activation +description: > + IxP activation — 6 positive activation tests (explicit, invoice-extraction, + receipt-processing, contract-extraction, form-extraction, generic) fanned + out via `dataset.rows`. Each row carries a distinct user prompt; success + criteria are identical across all rows. + + Scope: did the skill get called, not did it work. Assertions verify the agent + invoked the uipath-maestro-flow Skill and ran the right CLI-command patterns + (registry search for uipath.ixp.*, node-add against uipath.ixp.* or + core.logic.mock) — NOT whether the resulting flow is correct or would execute. + + Failure here is signal about SKILL.md or the IxP Plugin Index entry — + do NOT relax assertions; feed findings back in. + +tags: [uipath-maestro-flow, smoke, activation, ixp] + +task_timeout: 900 + +agent: + type: claude-code + max_turns: 40 + turn_timeout: 900 + +sandbox: + driver: tempdir + python: {} + +dataset: + rows: + - id: explicit + prompt: >- + I have a UiPath Flow. Add an IXP node that reads PDF invoices from a + SharePoint folder and extracts the structured fields so the next step + can post them to SAP. + + - id: invoice-extraction + prompt: >- + Build a UiPath Flow that pulls vendor name, invoice number, invoice + date, total amount, and line items out of PDF invoices saved in a + SharePoint folder, then posts the result to our accounting system. + + - id: receipt-processing + prompt: >- + Employees upload photos and PDFs of expense receipts. We need to pull + out the merchant name, transaction date, total, currency, and category + from each receipt and feed those into our expense reimbursement system. + + - id: contract-extraction + prompt: >- + Build a flow that reads our vendor contracts and pulls out the + counterparty name, effective date, term length in months, governing + law, and renewal clause text — then writes the results to a contracts + tracking spreadsheet. + + - id: form-extraction + prompt: >- + New vendors submit W-9 tax forms before we can pay them. Build a flow + that reads each submitted W-9 PDF, extracts the legal name, business + name, taxpayer ID number (TIN/SSN/EIN), tax classification, and + address, and writes the results to our vendor master record. + + - id: generic + prompt: >- + I want to extract 3 fields from an invoice that I store in my Google Drive. + +initial_prompt: | + ${row.prompt} + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors or rewrite the flow to fix them. A broken flow is acceptable. + - Exploration + an attempted node-add is enough — you do not need to build a working flow. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + # Greps the actual .flow artifact rather than the command stream — the + # command_executed checker truncates tool params at 2000 chars, which hides + # `"type": "core.logic.mock"` when the agent writes a large flow file in a + # single Write call. Per references/plugins/ixp/impl.md, the agent must land + # either an `uipath.ixp.*` node or a `core.logic.mock` placeholder. + # + # Searches `../..` rather than `.` so the assertion still passes if the agent + # `cd`s out of the stated sandbox dir before creating the solution. The cwd + # for run_command is the sandbox dir (coder_eval Sandbox.run_command), which + # for dataset-fanout tasks resolves to + # /artifacts/// + # so `../..` is the row's iteration `artifacts/` root and stays isolated + # from other rows. ASSUMES dataset fan-out (2-segment task_id); flattening + # this task to a non-dataset form would require dropping a `..`. + - type: run_command + description: "Agent added an IxP or core.logic.mock placeholder node to a .flow file (per references/plugins/ixp/impl.md)" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"(uipath\.ixp|core\.logic\.mock)" --include="*.flow" ../..' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml new file mode 100644 index 000000000..7cc51041b --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml @@ -0,0 +1,75 @@ +task_id: skill-flow-ixp-activation-listing +description: > + IxP listing activation — user asks what IxP models / runtime projects are + available from a Maestro flow. Read-only Q&A, not a build task. + + Asserts the agent invoked the uipath-maestro-flow Skill and ran the IxP + registry-search listing command (per references/plugins/ixp/impl.md — + Listing Published Models). Also asserts the agent did NOT take the build + path: no .flow file should be created for a listing question. + + Failure here is signal about the IxP plugin's Listing Published Models / + Listing Available Models sections — do NOT relax assertions, tighten the + copy. + +tags: [uipath-maestro-flow, smoke, activation, ixp, listing] + +# Listing is a one-shot Q&A — no need for the full activation budget. +task_timeout: 300 + +agent: + type: claude-code + max_turns: 20 + turn_timeout: 300 + +sandbox: + driver: tempdir + python: {} + +dataset: + rows: + - id: runtime-projects + prompt: >- + What runtime projects can I access in flow? + + - id: ixp-models + prompt: >- + What IxP models can I access in maestro? + + - id: published-extractors + prompt: >- + What document extractors can I access in flow? + +initial_prompt: | + ${row.prompt} + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types (listing path)" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + # Inverted check: a listing/Q&A task should NOT create a .flow file. + # Same `../..` rationale as activation.yaml — dataset-fanout cwd is + # /artifacts///. `! find ... | grep -q .` exits + # 0 when no .flow file is found (PASS) and 1 when one exists (FAIL). + - type: run_command + description: "No .flow file was created (listing is read-only Q&A, not a build task)" + command: '! find ../.. -name "*.flow" -type f | grep -q .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml new file mode 100644 index 000000000..1254c78a3 --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml @@ -0,0 +1,79 @@ +task_id: skill-flow-ixp-activation-negative +description: > + Negative IxP routing — verifies the agent does NOT add a uipath.ixp.* + node when the user asks for a Maestro flow with no document-extraction + scope. Parent uipath-maestro-flow Skill must still fire (this is in-skill + plugin-routing coverage; "should not fire maestro-flow at all" is upstream + in coder_eval/tasks/uipath_flow/triggering/triggering.jsonl). + + Each row carries a flow-shaped prompt that does not mention documents, + PDFs, invoices, receipts, contracts, forms, or field extraction. The IxP + plugin index / planning copy / SKILL.md must be tight enough that the + agent does not over-trigger. + + Failure here means activation copy is too broad — tighten the trigger + signals, do NOT relax assertions. + +tags: [uipath-maestro-flow, smoke, activation, ixp, negative] + +# Trimmed vs the positive activation suite (40 turns / 900s) — negative rows +# do not need to drive a full registry-search → node-add → validate loop. +task_timeout: 600 + +agent: + type: claude-code + max_turns: 20 + turn_timeout: 600 + +sandbox: + driver: tempdir + python: {} + +dataset: + rows: + + - id: stripe-http + prompt: >- + Create a flow with a manual trigger and an HTTP node that calls + api.stripe.com, then a script node that logs the response. + + - id: rpa-orchestration + prompt: >- + Create a Maestro flow that orchestrates an RPA process and an + inline agent in sequence. + + - id: structural-edit + prompt: >- + Add a decision branch and two end nodes to my existing .flow. + +initial_prompt: | + ${row.prompt} + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors or rewrite the flow to fix them. A broken flow is acceptable. + - Exploration + an attempted node-add is enough — you do not need to build a working flow. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Parent uipath-maestro-flow Skill fired (IxP-only routing is what this suite tests)" + weight: 1.0 + pass_threshold: 1.0 + + # Inverted twin of the positive activation.yaml grep. + # `shell=True` in coder_eval Sandbox.run_command means `!` works as a bash + # negator: grep -q exits 0 on match → ! flips to 1 (FAIL); exits 1 on + # no-match → ! flips to 0 (PASS). Same `../..` rationale as the positive + # test (dataset-fanout cwd is /artifacts///). + - type: run_command + description: "No uipath.ixp.* node landed in any .flow file (negative IxP routing)" + command: '! grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"uipath\.ixp" --include="*.flow" ../..' + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml new file mode 100644 index 000000000..801d7950a --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml @@ -0,0 +1,105 @@ +task_id: skill-flow-ixp-e2e-invoice-extraction-greenfield +description: > + E2E (greenfield): SharePoint trigger → IxP extraction → HTTP POST. Tests + end-to-end authoring of a real invoice-processing flow that monitors a + SharePoint folder, extracts structured invoice fields, and forwards the + result to a downstream system over HTTP. + + Validate-only: no `uip maestro flow debug`. IxP runtime + SharePoint + connector both require a tenant deployment which CI does not have; e2e + verifies offline structural correctness only (`uip maestro flow validate` + exit 0 on the generated flow). + +tags: [uipath-maestro-flow, e2e, lifecycle:generate, shape:multi-node, node:ixp, connector, feature:http] + +task_timeout: 900 + +agent: + type: claude-code + permission_mode: acceptEdits + max_turns: 80 + turn_timeout: 1200 + +sandbox: + driver: tempdir + python: {} + +initial_prompt: | + Build a UiPath Flow that monitors a SharePoint folder for new vendor PDF + invoices, extracts the structured data (invoice number, vendor, invoice + date, total amount, line items), and posts the result to SAP through an + HTTP request. + + - SharePoint connector: configured (use a connector trigger node) + - SharePoint folder: "New Vendor" + - IxP model: "Invoice Model" + - SAP: HTTP POST request to the configured endpoint, sending the extracted + invoice fields as the request body + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for a SharePoint connector" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*sharepoint' + min_count: 1 + weight: 1.5 + pass_threshold: 1.0 + + - type: run_command + description: "uip maestro flow validate passes on the generated flow file" + command: 'f=$(find . -name "*.flow" -print -quit); [ -n "$f" ] && uip maestro flow validate "$f" --output json' + timeout: 60 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains an IxP extraction node or core.logic.mock fallback" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"(uipath\.ixp|core\.logic\.mock)" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains a SharePoint trigger or connector node" + command: 'grep -rqiE "sharepoint" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains an HTTP action node (core.action.http)" + command: 'grep -rqE "core\.action\.http|\"type\"[[:space:]]*:[[:space:]]*\"[^\"]*http[^\"]*\"" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow references the IxP extraction output via $vars (downstream consumption)" + command: 'grep -rqE "\$vars\." --include="*.flow" .' + expected_exit_code: 0 + weight: 1.5 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml new file mode 100644 index 000000000..3904b87d5 --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml @@ -0,0 +1,297 @@ +task_id: skill-flow-ixp-e2e-project-selection +description: > + E2E (project selection): on a tenant where the demo IxP project set is + published (one model per zip in scripts/ixp-demo-projects/packages/), the + agent must pick the published model that matches the user's document + domain — not fall back to core.logic.mock and not pick a different + domain's model. + + Each row names a different document type (passport, receipt, bank + statement, …) and lists the fields the flow should extract. The prompts + deliberately do NOT name the IxP model, NodeType, or fully-qualified + name — picking the right entry from `registry search "uipath.ixp"` is + exactly the skill behaviour under test. + + Per-row strictness comes from a run_command that switches on the row dir + in $(pwd) (dataset-fanout cwd is /artifacts//[/]/, + see comment in activation.yaml) and asserts BOTH the chosen NodeType + prefix AND the inputs.modelName match the published model for that row. + + Validate-only: no `uip maestro flow debug`. The expected NodeType prefixes + and DisplayNames assume the runner publishes the IxP demo set as named + below — the source packages live outside this repo (CI runner / external + publisher), not under `scripts/`. If a publisher renames a project, + reconcile the case statement below against + `uip maestro flow registry search "uipath.ixp" --output json` on the + runner before merge. + +tags: [uipath-maestro-flow, e2e, lifecycle:generate, shape:single-node, node:ixp] + +task_timeout: 1200 + +agent: + type: claude-code + permission_mode: acceptEdits + max_turns: 60 + turn_timeout: 1200 + +sandbox: + driver: tempdir + python: {} + +dataset: + rows: + - id: aviation + prompt: >- + Build a Maestro flow that reads NTSB aviation accident investigation + final-report PDFs and extracts the NTSB number, event date, location + (city/state), aircraft make and model, and aircraft damage. Trigger + manually; the downstream script logs the NTSB number. + + - id: birth-certificate + prompt: >- + Build a Maestro flow that reads scanned birth certificates and + extracts the child's full name, date of birth, place of birth, and + parents' names. Trigger manually; the downstream script logs the + child's full name. + + - id: death-certificate + prompt: >- + Build a Maestro flow that reads scanned death certificates and + extracts the decedent's full name, date of death, place of death, + and cause of death. Trigger manually; the downstream script logs + the decedent's full name. + + - id: employment-agreement + prompt: >- + Build a Maestro flow that reads employment agreement PDFs and + extracts the employee name, employer name, start date, base salary, + and termination/severance terms. Trigger manually; the downstream + script logs the employee name. + + - id: health-insurance + prompt: >- + Build a Maestro flow that reads photos of health insurance cards + and extracts the member name, member ID, group number, insurer + name, and copay amounts. Trigger manually; the downstream script + logs the member ID. + + - id: bank-statement + prompt: >- + Build a Maestro flow that reads bank statement PDFs and extracts + the institution name, account number, statement period, and ending + balance. Trigger manually; the downstream script logs the + institution name. + + - id: passport + prompt: >- + Build a Maestro flow that reads passport scans and extracts the + holder's full name, passport number, nationality, date of birth, + and expiry date. Trigger manually; the downstream script logs the + passport number. + + - id: receipt + prompt: >- + Build a Maestro flow that reads expense receipt photos and + extracts the merchant name, transaction date, total amount, and + currency. Trigger manually; the downstream script logs the + merchant name. + + - id: icao + prompt: >- + Build a Maestro flow that reads the ICAO Air Transport Monthly + Monitor PDF and extracts the reporting period, top traffic figures, + and year-over-year change rates. Trigger manually; the downstream + script logs the reporting period. + + - id: loan-application + prompt: >- + Build a Maestro flow that reads Uniform Residential Loan + Application (Fannie Mae Form 1003) PDFs and extracts the borrower + name, current address, employment and income details, loan amount, + and subject property address. Trigger manually; the downstream + script logs the borrower name. + +initial_prompt: | + ${row.prompt} + + Important: + - The `uip` CLI is already available and the runner is logged in. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + # Same `../..` cwd rationale as activation.yaml:107-112: dataset-fanout cwd + # is /artifacts//[/]/, so artifacts written + # by the agent live two levels up at most. + - type: run_command + description: "uip maestro flow validate passes on the generated flow" + command: 'f=$(find ../.. -name "*.flow" -print -quit); [ -n "$f" ] && uip maestro flow validate "$f" --output json' + timeout: 60 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + # Real IxP node landed — agent did NOT fall back to mock on a tenant where + # a domain-matching IxP project IS published. + - type: run_command + description: "Generated flow contains a real uipath.ixp.* node" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"uipath\.ixp\." --include="*.flow" ../..' + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + # Inverted twin of the above. `! grep -q` exits 0 when no mock is found + # (PASS) and 1 when one exists (FAIL). Same `../..` rationale as above. + - type: run_command + description: "Generated flow does NOT contain a core.logic.mock fallback" + command: '! grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"core\.logic\.mock\"" --include="*.flow" ../..' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + # Per-row strict check: BOTH the chosen NodeType AND inputs.modelName must + # match the published model for this row's domain. Expected strings are + # pinned from `uip maestro flow registry search "uipath.ixp" --output json` + # against the CI runner's tenant. Re-pin if the publisher re-publishes — + # the hex IDs are runtime-project IDs assigned at publish time, not the + # dataset IDs in scripts/ixp-demo-projects/datasets.jsonl. + - type: run_command + description: "IxP node references the exact published model for this row's domain (strict)" + command: | + case "$(pwd)" in + *aviation*) + model='aviation-investigation-final-report-demo-a42b1d4d-ixp' + nt='uipath\.ixp\.aviation-investigation-final-report-demo-a42b1d4d-ixp\.shared-aviation-investigation-final-report-demo-a42b1d4d-ixp' + ;; + *birth*) + model='birth_certificates_oob-6252526a-ixp' + nt='uipath\.ixp\.birth-certificates-oob-6252526a-ixp\.shared-birth-certificates-oob-6252526a-ixp' + ;; + *death*) + model='death_certificates_oob-56f11e28-ixp' + nt='uipath\.ixp\.death-certificates-oob-56f11e28-ixp\.shared-death-certificates-oob-56f11e28-ixp' + ;; + *employment*) + model='employment-agreements-d4ee40a0-ixp' + nt='uipath\.ixp\.employment-agreements-d4ee40a0-ixp\.shared-employment-agreements-d4ee40a0-ixp' + ;; + *health-insurance*) + model='health_insurance_cards_oob-c28ec12f-ixp' + nt='uipath\.ixp\.health-insurance-cards-oob-c28ec12f-ixp\.shared-health-insurance-cards-oob-c28ec12f-ixp' + ;; + *bank-statement*) + model='idp-benchmark---bank-statements-819106fb-ixp' + nt='uipath\.ixp\.idp-benchmark-bank-statements-819106fb-ixp\.shared-idp-benchmark-bank-statements-819106fb-ixp' + ;; + *passport*) + model='idp-benchmark---passports-6b2a616e-ixp' + nt='uipath\.ixp\.idp-benchmark-passports-6b2a616e-ixp\.shared-idp-benchmark-passports-6b2a616e-ixp' + ;; + *receipt*) + model='idp-benchmark---receipts-43af6412-ixp' + nt='uipath\.ixp\.idp-benchmark-receipts-43af6412-ixp\.shared-idp-benchmark-receipts-43af6412-ixp' + ;; + *icao*) + model='ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp' + nt='uipath\.ixp\.ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp\.shared-ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp' + ;; + *loan*) + model='ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp' + nt='uipath\.ixp\.ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp\.shared-ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp' + ;; + *) + echo "Unknown row in pwd: $(pwd)" >&2 + exit 2 + ;; + esac + f=$(find ../.. -name "*.flow" -print -quit) + if [ -z "$f" ]; then + echo "no .flow file under ../.." >&2 + exit 1 + fi + if ! grep -qF "\"modelName\": \"${model}\"" "$f"; then + echo "modelName mismatch — expected '${model}' in $f" >&2 + grep -E '"modelName"' "$f" >&2 || true + exit 1 + fi + if ! grep -qE "\"type\"[[:space:]]*:[[:space:]]*\"${nt}" "$f"; then + echo "NodeType mismatch — expected prefix '${nt}' in $f" >&2 + grep -E '"type"[[:space:]]*:[[:space:]]*"uipath\.ixp' "$f" >&2 || true + exit 1 + fi + expected_exit_code: 0 + weight: 5.0 + pass_threshold: 1.0 + + # Structural IxP-node invariants that uip maestro flow validate does NOT + # catch (the IxP node manifest only marks fileRef as required, so the + # validator accepts instances that crash the Studio Web property panel + # or break runtime $vars resolution). See references/plugins/ixp/impl.md + # "JSON Structure" — Build procedure & Authoring rules #1, #4, #6. + - type: run_command + description: "IxP node passes the invariants flow validate misses: inputs.model object, outputs.{output,error}, no legacy fields, fileRef is a $vars expression" + command: | + f=$(find ../.. -name "*.flow" -print -quit) + if [ -z "$f" ]; then + echo "no .flow file under ../.." >&2 + exit 1 + fi + python3 - "$f" <<'PY' + import json, sys + path = sys.argv[1] + with open(path) as fh: + flow = json.load(fh) + ixp = [n for n in flow.get('nodes', []) if isinstance(n, dict) and isinstance(n.get('type'), str) and n['type'].startswith('uipath.ixp.')] + if not ixp: + print(f'no uipath.ixp.* node in {path}', file=sys.stderr); sys.exit(1) + errors = [] + forbidden = {'digitizationMode', 'documentTaxonomy', 'attachmentId', 'fileName', 'mimeType'} + for n in ixp: + nid = n.get('id', '') + inputs = n.get('inputs') or {} + outputs = n.get('outputs') or {} + model = inputs.get('model') + if not isinstance(model, dict): + errors.append(f"{nid}: inputs.model missing or not an object (canvas crashes when clicking the node — impl.md rule #1)") + else: + for k in ('modelName', 'folderKey'): + if not model.get(k): + errors.append(f"{nid}: inputs.model.{k} missing (ixp-model-taxonomy destructures it — impl.md rule #1)") + fileRef = inputs.get('fileRef', '') + if not isinstance(fileRef, str) or not fileRef.startswith('=js:$vars.'): + errors.append(f"{nid}: inputs.fileRef must be a '=js:$vars..output.' expression (impl.md rule #3)") + for port in ('output', 'error'): + if port not in outputs: + errors.append(f"{nid}: outputs.{port} missing — downstream $vars.{nid}.{port} will not resolve (impl.md rule #4)") + legacy = sorted(forbidden & set(inputs.keys())) + if legacy: + errors.append(f"{nid}: inputs contains forbidden legacy field(s) {legacy} — these were removed from the standalone IxP schema (impl.md rule #6)") + if 'model' in n and n.get('model') is not None: + errors.append(f"{nid}: top-level 'model' block on the instance — it must live only in definitions[] (impl.md rule #5)") + if errors: + for e in errors: + print(e, file=sys.stderr) + sys.exit(1) + PY + expected_exit_code: 0 + weight: 4.0 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml new file mode 100644 index 000000000..d3de750bc --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml @@ -0,0 +1,122 @@ +task_id: skill-flow-ixp-integration-handle-routing +description: > + Integration: IxP extraction wired into a Decision node that routes on a + field from the extracted content. Exercises field-level variable access + via the canonical IxP path + (`$vars.{ixpNode}.output.ExtractionResult.ResultsDocument.Fields.find(...)`) + and two-branch Decision wiring — the multi-node Quality scenario for IxP. + + Validate-only: no `uip maestro flow debug`. Validation in CI is offline + (`uip maestro flow validate`). + +tags: [uipath-maestro-flow, integration, lifecycle:generate, shape:multi-node, node:ixp, node:decision] + +task_timeout: 900 + +agent: + type: claude-code + permission_mode: acceptEdits + max_turns: 60 + turn_timeout: 900 + +sandbox: + driver: tempdir + python: {} + +initial_prompt: | + Build a UiPath Flow project named "InvoiceRouter" that processes vendor + invoices: + + 1. Manual trigger. + 2. IxP extraction node — extracts at least vendor name and total amount + from a PDF invoice using the model "Invoice Model" with the storage + bucket named "InvoiceBucket". + 3. Decision node — branches on the extracted total amount: if greater + than 1000, route to a "HighValueReview" script; otherwise route to an + "AutoApprove" script. + 4. Both downstream scripts contain a JavaScript that logs a one-line + summary including the extracted vendor name. + + Wire: Trigger → IxP extract → Decision → (two branches: HighValueReview + / AutoApprove). + + The Decision node condition MUST reference the extracted total amount + via the canonical IxP runtime path — + `$vars..output.ExtractionResult.ResultsDocument.Fields.find(f => f.FieldName === '')?.Values?.[0]` + — per references/plugins/ixp/impl.md ("Accessing Output"). + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "uip maestro flow validate passes on InvoiceRouter.flow" + command: "uip maestro flow validate InvoiceRouter/InvoiceRouter/InvoiceRouter.flow --output json" + timeout: 30 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains an IxP extraction node or core.logic.mock fallback" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"(uipath\.ixp|core\.logic\.mock)" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: file_contains + description: "Flow contains a Decision node (core.logic.decision)" + path: "InvoiceRouter/InvoiceRouter/InvoiceRouter.flow" + includes: + - "core.logic.decision" + weight: 2.0 + pass_threshold: 1.0 + + - type: file_contains + description: "Decision condition references the canonical IxP output path ($vars..output.ExtractionResult.ResultsDocument.Fields)" + path: "InvoiceRouter/InvoiceRouter/InvoiceRouter.flow" + includes: + - "$vars." + - "ExtractionResult" + - "ResultsDocument" + - "Fields" + weight: 3.0 + pass_threshold: 1.0 + + - type: file_contains + description: "Both branch script nodes are present by name" + path: "InvoiceRouter/InvoiceRouter/InvoiceRouter.flow" + includes: + - "HighValueReview" + - "AutoApprove" + weight: 2.5 + pass_threshold: 1.0 + + - type: file_contains + description: "Both Decision branches wired (true and false handles)" + path: "InvoiceRouter/InvoiceRouter/InvoiceRouter.flow" + includes: + - '"true"' + - '"false"' + weight: 2.0 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml new file mode 100644 index 000000000..78945b3ad --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml @@ -0,0 +1,87 @@ +task_id: skill-flow-ixp-smoke-scaffold-minimal +description: > + Smoke: minimal scaffold — manual trigger → IxP extract → script (logs "ok") + → validate. Tests that the agent picks the IxP plugin, authors a single + extraction node via Direct JSON, and produces a flow that passes + `uip maestro flow validate`. + + Validate-only: no `uip maestro flow debug`. IxP runtime requires a tenant + deployment which CI does not have; smoke verifies offline structural + correctness only. + +tags: [uipath-maestro-flow, smoke, lifecycle:generate, shape:single-node, node:ixp] + +agent: + type: claude-code + permission_mode: acceptEdits + max_turns: 40 + turn_timeout: 600 + +sandbox: + driver: tempdir + python: {} + +initial_prompt: | + Create a new Maestro Flow project called "Smoke" with a manual trigger, an + extract node, and a script node that just logs "ok". Wire the extracted + handle to the script and validate the flow. + + Document is in the storage bucket named: InvoiceBucket + Model used: Invoice Model + Script node: contains a JavaScript that logs OK when it receives the + extraction result. + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent validated the .flow file" + tool_name: "Bash" + command_pattern: 'uip\s+(maestro\s+)?flow\s+validate' + min_count: 1 + weight: 1.5 + pass_threshold: 1.0 + + - type: run_command + description: "uip maestro flow validate passes on Smoke.flow" + command: "uip maestro flow validate Smoke/Smoke/Smoke.flow --output json" + timeout: 30 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains an IxP extraction node or core.logic.mock fallback (per references/plugins/ixp/impl.md)" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"(uipath\.ixp|core\.logic\.mock)" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: file_contains + description: "Script node consumes the extraction output via $vars expression" + path: "Smoke/Smoke/Smoke.flow" + includes: + - "$vars." + weight: 1.5 + pass_threshold: 1.0 diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml b/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml new file mode 100644 index 000000000..043f0b344 --- /dev/null +++ b/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml @@ -0,0 +1,103 @@ +task_id: skill-flow-ixp-smoke-scaffold-multinode +description: > + Smoke: multi-script fan-out from an IxP extraction — manual trigger → + IxP extract → 3 script nodes (postReceipt, sendToValidation, logError). + Tests that the agent picks IxP, authors the extraction node, and wires + three downstream scripts that consume the extraction result. + + Note on port shape: per references/plugins/ixp/impl.md, IxP exposes a single + output port `success` plus an `error` output variable in `$vars.{nodeId}`. + The "extracted / needsValidation / error" handles map onto that shape via + fan-out from the success port and per-script use of + `$vars.{ixpNode}.output.ExtractionResult.ResultsDocument.Fields[…]` (the + canonical access path) vs `$vars.{ixpNode}.error`. Assertions accept any + valid wiring that yields three named script nodes plus a passing flow + validate. + + Validate-only: no `uip maestro flow debug`. + +tags: [uipath-maestro-flow, smoke, lifecycle:generate, shape:multi-node, node:ixp] + +agent: + type: claude-code + permission_mode: acceptEdits + max_turns: 40 + turn_timeout: 600 + +sandbox: + driver: tempdir + python: {} + +initial_prompt: | + Create a flow called "ReceiptIntake" with a manual trigger, an extractor + node, and add three script nodes named `postReceipt`, `sendToValidation`, + and `logError`. Wire all three scripts to consume the extraction result — + two off the success output, one off the error output. Document is in the + storage bucket named: ReceiptBucket. Model used: Receipt Model. Each of + the three script nodes logs OK when it receives its respective extraction + result. + + Important: + - The `uip` CLI is already available. + - Use `--output json` on all uip commands. + - Work inside the stated working directory — do NOT `cd` to a parent. + - Do NOT run `uip maestro flow debug` or `uip maestro flow deploy`. + - Run `uip maestro flow validate` at most ONCE. If it fails, stop. + - Do NOT iterate on validation errors. + +success_criteria: + - type: skill_triggered + expected: "yes" + skill_name: "uipath:uipath-maestro-flow" + description: "Agent invoked the uipath-maestro-flow Skill" + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent searched the registry for IxP node types" + tool_name: "Bash" + command_pattern: 'uip\s+maestro\s+flow\s+registry\s+(search|list|get)\b[^\n]*ixp' + min_count: 1 + weight: 2.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent validated the .flow file" + tool_name: "Bash" + command_pattern: 'uip\s+(maestro\s+)?flow\s+validate' + min_count: 1 + weight: 1.5 + pass_threshold: 1.0 + + - type: run_command + description: "uip maestro flow validate passes on ReceiptIntake.flow" + command: "uip maestro flow validate ReceiptIntake/ReceiptIntake/ReceiptIntake.flow --output json" + timeout: 30 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "Generated flow contains an IxP extraction node or core.logic.mock fallback (per references/plugins/ixp/impl.md)" + command: 'grep -rqE "\"type\"[[:space:]]*:[[:space:]]*\"(uipath\.ixp|core\.logic\.mock)" --include="*.flow" .' + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: file_contains + description: "All three script nodes are present by name" + path: "ReceiptIntake/ReceiptIntake/ReceiptIntake.flow" + includes: + - "postReceipt" + - "sendToValidation" + - "logError" + weight: 2.5 + pass_threshold: 1.0 + + - type: file_contains + description: "Scripts reference the extraction output via $vars expression" + path: "ReceiptIntake/ReceiptIntake/ReceiptIntake.flow" + includes: + - "$vars." + weight: 1.5 + pass_threshold: 1.0 From 90954cb183ac6fae7d0b8b5699fe8d850dfdc361 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 6 May 2026 11:57:18 +0100 Subject: [PATCH 2/7] docs(uipath-maestro-flow): address IxP plugin review feedback - Move IxP test suite from single_node/ixp/ to ixp/ (matches uipath-data-fabric layout; the suite contains multi-node and e2e flows that don't belong under single_node). - Replace impl.md self-check heredoc with a single invocation of the canonical self-check.py (the heredoc duplicated the script logic). - Trim activation/listing/negative test rows (6/3/3 to 2/1/1) to reduce daily-run cost. - Add task_timeout: 600 to smoke_01 and smoke_02. - Trim SKILL.md description: drop "document" and "receipts" to free flow-keyword tokens. - Update CODEOWNERS path to match the new test dir. --- CODEOWNERS | 2 +- .../author/references/plugins/ixp/impl.md | 38 ++----------------- .../references/plugins/ixp/self-check.py | 8 ---- .../{single_node => }/ixp/activation.yaml | 29 +------------- .../ixp/activation_listing.yaml | 8 ---- .../ixp/activation_negative.yaml | 9 ----- .../e2e_01_invoice_extraction_greenfield.yaml | 0 .../ixp/e2e_02_project_selection.yaml | 0 .../ixp/integration_handle_routing.yaml | 0 .../ixp/smoke_01_scaffold_minimal.yaml | 2 + .../ixp/smoke_02_scaffold_multinode.yaml | 2 + 11 files changed, 10 insertions(+), 88 deletions(-) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/activation.yaml (73%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/activation_listing.yaml (91%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/activation_negative.yaml (91%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/e2e_01_invoice_extraction_greenfield.yaml (100%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/e2e_02_project_selection.yaml (100%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/integration_handle_routing.yaml (100%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/smoke_01_scaffold_minimal.yaml (99%) rename tests/tasks/uipath-maestro-flow/{single_node => }/ixp/smoke_02_scaffold_multinode.yaml (99%) diff --git a/CODEOWNERS b/CODEOWNERS index ea25a4999..8564c784d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -57,7 +57,7 @@ # IXP /skills/uipath-maestro-flow/references/author/references/plugins/ixp/ @jcalero @constantinmuraru @scallaway-uipath @tommilligan @joe-prosser @vlad-crisan @richsilv @AWilcke -/tests/tasks/uipath-maestro-flow/single_node/ixp/ @jcalero @constantinmuraru @scallaway-uipath @tommilligan @joe-prosser @vlad-crisan @richsilv @AWilcke +/tests/tasks/uipath-maestro-flow/ixp/ @jcalero @constantinmuraru @scallaway-uipath @tommilligan @joe-prosser @vlad-crisan @richsilv @AWilcke # Review skill /skills/uipath-review/ @AlvinStanescu @gabrielavaduva @roalexandru diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md index 5afc426c7..fffb4b7a9 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -197,46 +197,14 @@ The `definitions[]` entry is copied verbatim from `registry get` (`Data.Node`). ### Self-check before `uip maestro flow validate` -`uip maestro flow validate` only checks structural conformance to the node manifest, and the IxP manifest declares `inputDefinition.required: ["fileRef"]` — so a flow with `inputs.model` undefined, missing `outputs.error`, or legacy fields in `inputs` passes `validate` cleanly and then crashes Studio Web's property panel at click time (`Cannot destructure property 'modelName' of 't' as it is undefined`). Run this self-check on the `.flow` file you wrote, **before** `flow validate`. +`uip maestro flow validate` only checks structural conformance to the node manifest, and the IxP manifest declares `inputDefinition.required: ["fileRef"]` — so a flow with `inputs.model` undefined, missing `outputs.error`, or legacy fields in `inputs` passes `validate` cleanly and then crashes Studio Web's property panel at click time (`Cannot destructure property 'modelName' of 't' as it is undefined`). Run [`self-check.py`](self-check.py) on the `.flow` file you wrote, **before** `flow validate`. -> **DO NOT MODIFY THIS SCRIPT.** Run it as-is. Do NOT add suppressions, downgrade errors to warnings, remove `sys.exit(1)`, or rephrase the failure messages. The heredoc below is a flattened, function-free copy of [`self-check.py`](self-check.py) in this directory — the two forms are not byte-equivalent (the `.py` wraps the logic in `check_flow()` / `main()`, the heredoc inlines it) but they enforce the same rules with the same exit codes. CI runs the same logic against the generated `.flow`. A modified self-check that exits 0 is treated as a failed run, not a pass. +> **DO NOT MODIFY OR REWRITE THIS SCRIPT.** Run it as-is. Do NOT inline its logic into the agent loop, add suppressions, downgrade errors to warnings, or rephrase the failure messages. CI runs the same script against the generated `.flow`. A modified self-check that exits 0 is treated as a failed run, not a pass. > > The Authoring rules above are the source of truth for the instance contract. The registry's `inputDefinition.properties` is **not** a license to override them: it is the schema of the property catalog (current keys only — `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, `mimeType` are NOT returned by `registry get`). If a forbidden field appears in your `inputs`, you put it there from training-data recall, not from the registry response. ```bash -python3 - "//.flow" <<'PY' -import json, sys -FORBIDDEN_INPUT_FIELDS = {"digitizationMode", "documentTaxonomy", "attachmentId", "fileName", "mimeType"} -with open(sys.argv[1]) as fh: - flow = json.load(fh) -errors = [] -for node in flow.get("nodes") or []: - if not isinstance(node, dict) or not str(node.get("type", "")).startswith("uipath.ixp."): - continue - nid = node.get("id", "?") - inputs = node.get("inputs") or {} - outputs = node.get("outputs") or {} - model = inputs.get("model") - if not isinstance(model, dict) or not model.get("modelName") or not model.get("folderKey"): - errors.append(f"{nid}: rule #1 — inputs.model.{{modelName,folderKey}} must be present (canvas crashes with 'Cannot destructure property modelName' otherwise)") - fileRef = inputs.get("fileRef", "") - if not isinstance(fileRef, str) or not fileRef.startswith("=js:$vars."): - errors.append(f"{nid}: rule #3 — inputs.fileRef must be '=js:$vars..output.'") - for port in ("output", "error"): - if port not in outputs: - errors.append(f"{nid}: rule #4 — outputs.{port} required (downstream $vars.{nid}.{port} won't resolve)") - legacy = sorted(FORBIDDEN_INPUT_FIELDS & set(inputs.keys())) - if legacy: - errors.append(f"{nid}: rule #6 — forbidden legacy fields in inputs: {legacy} (removed from current schema; registry-get does NOT return these)") - if node.get("model") is not None: - errors.append(f"{nid}: rule #5 — top-level 'model' on instance (must live in definitions[])") -if errors: - print("IxP self-check FAILED", file=sys.stderr) - for e in errors: - print(f" {e}", file=sys.stderr) - sys.exit(1) -print("IxP self-check passed.") -PY +python3 "/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py" //.flow ``` Procedure: diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py index e17965be3..45d80ab96 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py @@ -10,14 +10,6 @@ runtime invariants (Studio Web canvas destructure, `$vars` resolution, schema drift) that `flow validate` cannot catch. A failure here means the `.flow` file is broken — fix the flow, not the check. - -The inline heredoc form embedded in impl.md ("Self-check before -`uip maestro flow validate`") is a flattened, function-free version of -the same checks. The two forms are NOT byte-equivalent — this file -wraps the logic in `check_flow()` / `main()`, the heredoc inlines it — -but they MUST stay logically equivalent: the same FORBIDDEN_INPUT_FIELDS -set, the same per-node assertions, the same rule numbers, the same exit -codes. When changing one, change the other. """ from __future__ import annotations diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml b/tests/tasks/uipath-maestro-flow/ixp/activation.yaml similarity index 73% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml rename to tests/tasks/uipath-maestro-flow/ixp/activation.yaml index b66fa41fa..ce61c74cc 100644 --- a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/activation.yaml @@ -1,8 +1,7 @@ task_id: skill-flow-ixp-activation description: > - IxP activation — 6 positive activation tests (explicit, invoice-extraction, - receipt-processing, contract-extraction, form-extraction, generic) fanned - out via `dataset.rows`. Each row carries a distinct user prompt; success + IxP activation — 2 positive activation tests (explicit, invoice-extraction) + fanned out via `dataset.rows`. Each row carries a distinct user prompt; success criteria are identical across all rows. Scope: did the skill get called, not did it work. Assertions verify the agent @@ -40,30 +39,6 @@ dataset: date, total amount, and line items out of PDF invoices saved in a SharePoint folder, then posts the result to our accounting system. - - id: receipt-processing - prompt: >- - Employees upload photos and PDFs of expense receipts. We need to pull - out the merchant name, transaction date, total, currency, and category - from each receipt and feed those into our expense reimbursement system. - - - id: contract-extraction - prompt: >- - Build a flow that reads our vendor contracts and pulls out the - counterparty name, effective date, term length in months, governing - law, and renewal clause text — then writes the results to a contracts - tracking spreadsheet. - - - id: form-extraction - prompt: >- - New vendors submit W-9 tax forms before we can pay them. Build a flow - that reads each submitted W-9 PDF, extracts the legal name, business - name, taxpayer ID number (TIN/SSN/EIN), tax classification, and - address, and writes the results to our vendor master record. - - - id: generic - prompt: >- - I want to extract 3 fields from an invoice that I store in my Google Drive. - initial_prompt: | ${row.prompt} diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml b/tests/tasks/uipath-maestro-flow/ixp/activation_listing.yaml similarity index 91% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml rename to tests/tasks/uipath-maestro-flow/ixp/activation_listing.yaml index 7cc51041b..03e6ac1bd 100644 --- a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_listing.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/activation_listing.yaml @@ -28,18 +28,10 @@ sandbox: dataset: rows: - - id: runtime-projects - prompt: >- - What runtime projects can I access in flow? - - id: ixp-models prompt: >- What IxP models can I access in maestro? - - id: published-extractors - prompt: >- - What document extractors can I access in flow? - initial_prompt: | ${row.prompt} diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml b/tests/tasks/uipath-maestro-flow/ixp/activation_negative.yaml similarity index 91% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml rename to tests/tasks/uipath-maestro-flow/ixp/activation_negative.yaml index 1254c78a3..3aa8b64f4 100644 --- a/tests/tasks/uipath-maestro-flow/single_node/ixp/activation_negative.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/activation_negative.yaml @@ -37,15 +37,6 @@ dataset: Create a flow with a manual trigger and an HTTP node that calls api.stripe.com, then a script node that logs the response. - - id: rpa-orchestration - prompt: >- - Create a Maestro flow that orchestrates an RPA process and an - inline agent in sequence. - - - id: structural-edit - prompt: >- - Add a decision branch and two end nodes to my existing .flow. - initial_prompt: | ${row.prompt} diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml b/tests/tasks/uipath-maestro-flow/ixp/e2e_01_invoice_extraction_greenfield.yaml similarity index 100% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_01_invoice_extraction_greenfield.yaml rename to tests/tasks/uipath-maestro-flow/ixp/e2e_01_invoice_extraction_greenfield.yaml diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml b/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml similarity index 100% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/e2e_02_project_selection.yaml rename to tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml b/tests/tasks/uipath-maestro-flow/ixp/integration_handle_routing.yaml similarity index 100% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/integration_handle_routing.yaml rename to tests/tasks/uipath-maestro-flow/ixp/integration_handle_routing.yaml diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml b/tests/tasks/uipath-maestro-flow/ixp/smoke_01_scaffold_minimal.yaml similarity index 99% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml rename to tests/tasks/uipath-maestro-flow/ixp/smoke_01_scaffold_minimal.yaml index 78945b3ad..610f1b045 100644 --- a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_01_scaffold_minimal.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/smoke_01_scaffold_minimal.yaml @@ -11,6 +11,8 @@ description: > tags: [uipath-maestro-flow, smoke, lifecycle:generate, shape:single-node, node:ixp] +task_timeout: 600 + agent: type: claude-code permission_mode: acceptEdits diff --git a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml b/tests/tasks/uipath-maestro-flow/ixp/smoke_02_scaffold_multinode.yaml similarity index 99% rename from tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml rename to tests/tasks/uipath-maestro-flow/ixp/smoke_02_scaffold_multinode.yaml index 043f0b344..e840bdf51 100644 --- a/tests/tasks/uipath-maestro-flow/single_node/ixp/smoke_02_scaffold_multinode.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/smoke_02_scaffold_multinode.yaml @@ -18,6 +18,8 @@ description: > tags: [uipath-maestro-flow, smoke, lifecycle:generate, shape:multi-node, node:ixp] +task_timeout: 600 + agent: type: claude-code permission_mode: acceptEdits From c416b8a1794b5b37e2bd741af41ca0f99c0bd5c6 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 6 May 2026 11:57:26 +0100 Subject: [PATCH 3/7] test(uipath-maestro-flow): trim ixp e2e_02 rows and dedupe self-check - Drop 8 of 10 dataset rows (kept aviation, birth-certificate); shrink the case statement to match. - Replace the inline self-check Python with a call to the canonical self-check.py shipped alongside impl.md. --- .../ixp/e2e_02_project_selection.yaml | 142 +----------------- 1 file changed, 7 insertions(+), 135 deletions(-) diff --git a/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml b/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml index 3904b87d5..9fa850349 100644 --- a/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml @@ -6,8 +6,8 @@ description: > domain — not fall back to core.logic.mock and not pick a different domain's model. - Each row names a different document type (passport, receipt, bank - statement, …) and lists the fields the flow should extract. The prompts + Each row names a different document type (aviation report, birth + certificate) and lists the fields the flow should extract. The prompts deliberately do NOT name the IxP model, NodeType, or fully-qualified name — picking the right entry from `registry search "uipath.ixp"` is exactly the skill behaviour under test. @@ -55,63 +55,6 @@ dataset: parents' names. Trigger manually; the downstream script logs the child's full name. - - id: death-certificate - prompt: >- - Build a Maestro flow that reads scanned death certificates and - extracts the decedent's full name, date of death, place of death, - and cause of death. Trigger manually; the downstream script logs - the decedent's full name. - - - id: employment-agreement - prompt: >- - Build a Maestro flow that reads employment agreement PDFs and - extracts the employee name, employer name, start date, base salary, - and termination/severance terms. Trigger manually; the downstream - script logs the employee name. - - - id: health-insurance - prompt: >- - Build a Maestro flow that reads photos of health insurance cards - and extracts the member name, member ID, group number, insurer - name, and copay amounts. Trigger manually; the downstream script - logs the member ID. - - - id: bank-statement - prompt: >- - Build a Maestro flow that reads bank statement PDFs and extracts - the institution name, account number, statement period, and ending - balance. Trigger manually; the downstream script logs the - institution name. - - - id: passport - prompt: >- - Build a Maestro flow that reads passport scans and extracts the - holder's full name, passport number, nationality, date of birth, - and expiry date. Trigger manually; the downstream script logs the - passport number. - - - id: receipt - prompt: >- - Build a Maestro flow that reads expense receipt photos and - extracts the merchant name, transaction date, total amount, and - currency. Trigger manually; the downstream script logs the - merchant name. - - - id: icao - prompt: >- - Build a Maestro flow that reads the ICAO Air Transport Monthly - Monitor PDF and extracts the reporting period, top traffic figures, - and year-over-year change rates. Trigger manually; the downstream - script logs the reporting period. - - - id: loan-application - prompt: >- - Build a Maestro flow that reads Uniform Residential Loan - Application (Fannie Mae Form 1003) PDFs and extracts the borrower - name, current address, employment and income details, loan amount, - and subject property address. Trigger manually; the downstream - script logs the borrower name. - initial_prompt: | ${row.prompt} @@ -186,38 +129,6 @@ success_criteria: model='birth_certificates_oob-6252526a-ixp' nt='uipath\.ixp\.birth-certificates-oob-6252526a-ixp\.shared-birth-certificates-oob-6252526a-ixp' ;; - *death*) - model='death_certificates_oob-56f11e28-ixp' - nt='uipath\.ixp\.death-certificates-oob-56f11e28-ixp\.shared-death-certificates-oob-56f11e28-ixp' - ;; - *employment*) - model='employment-agreements-d4ee40a0-ixp' - nt='uipath\.ixp\.employment-agreements-d4ee40a0-ixp\.shared-employment-agreements-d4ee40a0-ixp' - ;; - *health-insurance*) - model='health_insurance_cards_oob-c28ec12f-ixp' - nt='uipath\.ixp\.health-insurance-cards-oob-c28ec12f-ixp\.shared-health-insurance-cards-oob-c28ec12f-ixp' - ;; - *bank-statement*) - model='idp-benchmark---bank-statements-819106fb-ixp' - nt='uipath\.ixp\.idp-benchmark-bank-statements-819106fb-ixp\.shared-idp-benchmark-bank-statements-819106fb-ixp' - ;; - *passport*) - model='idp-benchmark---passports-6b2a616e-ixp' - nt='uipath\.ixp\.idp-benchmark-passports-6b2a616e-ixp\.shared-idp-benchmark-passports-6b2a616e-ixp' - ;; - *receipt*) - model='idp-benchmark---receipts-43af6412-ixp' - nt='uipath\.ixp\.idp-benchmark-receipts-43af6412-ixp\.shared-idp-benchmark-receipts-43af6412-ixp' - ;; - *icao*) - model='ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp' - nt='uipath\.ixp\.ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp\.shared-ixp-icao-air-transport-monthly-monitor-customer-facing-friendly-2520735a-ixp' - ;; - *loan*) - model='ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp' - nt='uipath\.ixp\.ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp\.shared-ixp-uniform-residential-loan-application-fannie-mae-form-1003-ec9bb5b0-ixp' - ;; *) echo "Unknown row in pwd: $(pwd)" >&2 exit 2 @@ -245,53 +156,14 @@ success_criteria: # Structural IxP-node invariants that uip maestro flow validate does NOT # catch (the IxP node manifest only marks fileRef as required, so the # validator accepts instances that crash the Studio Web property panel - # or break runtime $vars resolution). See references/plugins/ixp/impl.md - # "JSON Structure" — Build procedure & Authoring rules #1, #4, #6. + # or break runtime $vars resolution). Delegated to the canonical + # self-check.py shipped with the skill. - type: run_command - description: "IxP node passes the invariants flow validate misses: inputs.model object, outputs.{output,error}, no legacy fields, fileRef is a $vars expression" + description: "IxP node passes the invariants flow validate misses (delegated to self-check.py)" command: | f=$(find ../.. -name "*.flow" -print -quit) - if [ -z "$f" ]; then - echo "no .flow file under ../.." >&2 - exit 1 - fi - python3 - "$f" <<'PY' - import json, sys - path = sys.argv[1] - with open(path) as fh: - flow = json.load(fh) - ixp = [n for n in flow.get('nodes', []) if isinstance(n, dict) and isinstance(n.get('type'), str) and n['type'].startswith('uipath.ixp.')] - if not ixp: - print(f'no uipath.ixp.* node in {path}', file=sys.stderr); sys.exit(1) - errors = [] - forbidden = {'digitizationMode', 'documentTaxonomy', 'attachmentId', 'fileName', 'mimeType'} - for n in ixp: - nid = n.get('id', '') - inputs = n.get('inputs') or {} - outputs = n.get('outputs') or {} - model = inputs.get('model') - if not isinstance(model, dict): - errors.append(f"{nid}: inputs.model missing or not an object (canvas crashes when clicking the node — impl.md rule #1)") - else: - for k in ('modelName', 'folderKey'): - if not model.get(k): - errors.append(f"{nid}: inputs.model.{k} missing (ixp-model-taxonomy destructures it — impl.md rule #1)") - fileRef = inputs.get('fileRef', '') - if not isinstance(fileRef, str) or not fileRef.startswith('=js:$vars.'): - errors.append(f"{nid}: inputs.fileRef must be a '=js:$vars..output.' expression (impl.md rule #3)") - for port in ('output', 'error'): - if port not in outputs: - errors.append(f"{nid}: outputs.{port} missing — downstream $vars.{nid}.{port} will not resolve (impl.md rule #4)") - legacy = sorted(forbidden & set(inputs.keys())) - if legacy: - errors.append(f"{nid}: inputs contains forbidden legacy field(s) {legacy} — these were removed from the standalone IxP schema (impl.md rule #6)") - if 'model' in n and n.get('model') is not None: - errors.append(f"{nid}: top-level 'model' block on the instance — it must live only in definitions[] (impl.md rule #5)") - if errors: - for e in errors: - print(e, file=sys.stderr) - sys.exit(1) - PY + [ -n "$f" ] || { echo "no .flow file under ../.." >&2; exit 1; } + python3 "$SKILLS_REPO_PATH/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py" "$f" expected_exit_code: 0 weight: 4.0 pass_threshold: 1.0 From 27278a1507f8ea59877380f2ad218cbe08b0200f Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 6 May 2026 16:37:57 +0100 Subject: [PATCH 4/7] docs(uipath-maestro-flow): drop ixp self-check workaround Delete `self-check.py` and the "Self-check before uip maestro flow validate" section in `impl.md`. The rules now run inside `uip maestro flow validate` via the new `ixpNodeValidator` (UiPath/cli#1866). Drops the corresponding `run_command` stanza from `e2e_02_project_selection.yaml`. --- .../author/references/plugins/ixp/impl.md | 20 +---- .../references/plugins/ixp/self-check.py | 83 ------------------- .../ixp/e2e_02_project_selection.yaml | 15 ---- 3 files changed, 1 insertion(+), 117 deletions(-) delete mode 100644 skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md index fffb4b7a9..b9acc235b 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -195,25 +195,7 @@ If you find yourself typing any of those five field names while authoring an IxP The `definitions[]` entry is copied verbatim from `registry get` (`Data.Node`). Critical Rule #7 applies unchanged. -### Self-check before `uip maestro flow validate` - -`uip maestro flow validate` only checks structural conformance to the node manifest, and the IxP manifest declares `inputDefinition.required: ["fileRef"]` — so a flow with `inputs.model` undefined, missing `outputs.error`, or legacy fields in `inputs` passes `validate` cleanly and then crashes Studio Web's property panel at click time (`Cannot destructure property 'modelName' of 't' as it is undefined`). Run [`self-check.py`](self-check.py) on the `.flow` file you wrote, **before** `flow validate`. - -> **DO NOT MODIFY OR REWRITE THIS SCRIPT.** Run it as-is. Do NOT inline its logic into the agent loop, add suppressions, downgrade errors to warnings, or rephrase the failure messages. CI runs the same script against the generated `.flow`. A modified self-check that exits 0 is treated as a failed run, not a pass. -> -> The Authoring rules above are the source of truth for the instance contract. The registry's `inputDefinition.properties` is **not** a license to override them: it is the schema of the property catalog (current keys only — `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, `mimeType` are NOT returned by `registry get`). If a forbidden field appears in your `inputs`, you put it there from training-data recall, not from the registry response. - -```bash -python3 "/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py" //.flow -``` - -Procedure: - -1. **Exit 0** → proceed to `uip maestro flow validate .flow --output json`. -2. **Exit 1** → read each error line; the rule number tells you which Authoring rule above to re-read. Fix the **`.flow` file**, not the script. The fix is almost always one of: copy `inputs.model` verbatim from the `registry get` response you already fetched, copy `outputs.{output,error}` verbatim from `outputDefinition`, or delete a legacy field from `inputs`. Re-run the self-check. -3. **Maximum 2 fix attempts.** If the third self-check still fails, stop and surface the remaining errors to the user — do not declare the flow done, do not run `flow validate` to paper over the failure, and do not edit the script to make it pass. - -If you find yourself reading the script and considering a "this rule doesn't apply because the registry says..." rationalization: stop. The registry response is in your transcript; grep it for the field name. If it isn't there (and for the five forbidden fields it never is), the rule applies. Run the script unmodified. +> **`uip maestro flow validate` enforces the Authoring rules above** via the `ixp-node` validator. If validation fails, the error path/message names the rule (e.g. `Rule #1 ...`) — fix the `.flow` file, not the validator. The registry's `inputDefinition.properties` is the schema of the property catalog, not a license to override the rules: `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, and `mimeType` are NOT returned by `registry get` and must not be set on the instance. ### `inputs.fileRef` vs the emitted `model.inputs[]` body diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py deleted file mode 100644 index 45d80ab96..000000000 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -""" -IxP node self-check — canonical source. - -Enforces Authoring rules #1, #3, #4, #5, #6 from impl.md. Run on the -generated `.flow` file BEFORE `uip maestro flow validate`. Exits 0 when -the file is clean, 1 when any rule is violated. - -DO NOT MODIFY THIS SCRIPT TO SUPPRESS A FAILURE. The rules encode -runtime invariants (Studio Web canvas destructure, `$vars` resolution, -schema drift) that `flow validate` cannot catch. A failure here means -the `.flow` file is broken — fix the flow, not the check. -""" -from __future__ import annotations - -import json -import sys - - -FORBIDDEN_INPUT_FIELDS = { - "digitizationMode", - "documentTaxonomy", - "attachmentId", - "fileName", - "mimeType", -} - - -def check_flow(path: str) -> list[str]: - with open(path) as fh: - flow = json.load(fh) - errors: list[str] = [] - for node in flow.get("nodes") or []: - if not isinstance(node, dict): - continue - if not str(node.get("type", "")).startswith("uipath.ixp."): - continue - nid = node.get("id", "?") - inputs = node.get("inputs") or {} - outputs = node.get("outputs") or {} - model = inputs.get("model") - if not isinstance(model, dict) or not model.get("modelName") or not model.get("folderKey"): - errors.append( - f"{nid}: rule #1 — inputs.model.{{modelName,folderKey}} must be present " - f"(canvas crashes with 'Cannot destructure property modelName' otherwise)" - ) - fileRef = inputs.get("fileRef", "") - if not isinstance(fileRef, str) or not fileRef.startswith("=js:$vars."): - errors.append( - f"{nid}: rule #3 — inputs.fileRef must be '=js:$vars..output.'" - ) - for port in ("output", "error"): - if port not in outputs: - errors.append( - f"{nid}: rule #4 — outputs.{port} required (downstream $vars.{nid}.{port} won't resolve)" - ) - legacy = sorted(FORBIDDEN_INPUT_FIELDS & set(inputs.keys())) - if legacy: - errors.append( - f"{nid}: rule #6 — forbidden legacy fields in inputs: {legacy} " - f"(removed from current schema; registry-get does NOT return these)" - ) - if node.get("model") is not None: - errors.append(f"{nid}: rule #5 — top-level 'model' on instance (must live in definitions[])") - return errors - - -def main() -> int: - if len(sys.argv) != 2: - print("usage: self-check.py .flow", file=sys.stderr) - return 2 - errors = check_flow(sys.argv[1]) - if errors: - print("IxP self-check FAILED", file=sys.stderr) - for e in errors: - print(f" {e}", file=sys.stderr) - return 1 - print("IxP self-check passed.") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml b/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml index 9fa850349..b3a22d27b 100644 --- a/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml +++ b/tests/tasks/uipath-maestro-flow/ixp/e2e_02_project_selection.yaml @@ -152,18 +152,3 @@ success_criteria: expected_exit_code: 0 weight: 5.0 pass_threshold: 1.0 - - # Structural IxP-node invariants that uip maestro flow validate does NOT - # catch (the IxP node manifest only marks fileRef as required, so the - # validator accepts instances that crash the Studio Web property panel - # or break runtime $vars resolution). Delegated to the canonical - # self-check.py shipped with the skill. - - type: run_command - description: "IxP node passes the invariants flow validate misses (delegated to self-check.py)" - command: | - f=$(find ../.. -name "*.flow" -print -quit) - [ -n "$f" ] || { echo "no .flow file under ../.." >&2; exit 1; } - python3 "$SKILLS_REPO_PATH/skills/uipath-maestro-flow/references/author/references/plugins/ixp/self-check.py" "$f" - expected_exit_code: 0 - weight: 4.0 - pass_threshold: 1.0 From 596af4d49e2b91b508dff453c08cba5020a04866 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 6 May 2026 16:58:49 +0100 Subject: [PATCH 5/7] docs(uipath-maestro-flow): align ixp callout with validator error format UiPath/cli#1866's `ixpNodeValidator` emits self-contained error messages with `path` like `nodes[].inputs.model` (not "Rule #1 ..."), so the impl.md callout was misdescribing what an agent will see when `flow validate` fails on an IxP node. --- .../references/author/references/plugins/ixp/impl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md index b9acc235b..75349a1c2 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -195,7 +195,7 @@ If you find yourself typing any of those five field names while authoring an IxP The `definitions[]` entry is copied verbatim from `registry get` (`Data.Node`). Critical Rule #7 applies unchanged. -> **`uip maestro flow validate` enforces the Authoring rules above** via the `ixp-node` validator. If validation fails, the error path/message names the rule (e.g. `Rule #1 ...`) — fix the `.flow` file, not the validator. The registry's `inputDefinition.properties` is the schema of the property catalog, not a license to override the rules: `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, and `mimeType` are NOT returned by `registry get` and must not be set on the instance. +> **`uip maestro flow validate` enforces the Authoring rules above** via the `ixp-node` validator. Failures surface as `severity: "error"` issues with `path` like `nodes[].inputs.model` and a self-contained `message` describing the violation — fix the `.flow` file, not the validator. The registry's `inputDefinition.properties` is the schema of the property catalog, not a license to override the rules: `digitizationMode`, `documentTaxonomy`, `attachmentId`, `fileName`, and `mimeType` are NOT returned by `registry get` and must not be set on the instance. ### `inputs.fileRef` vs the emitted `model.inputs[]` body From dbe3082dee332864d1b50d943410132c51d7c3cd Mon Sep 17 00:00:00 2001 From: joe-prosser Date: Fri, 22 May 2026 13:07:42 +0100 Subject: [PATCH 6/7] Update skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md Co-authored-by: Rocky Madden --- .../references/author/references/plugins/ixp/impl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md index 75349a1c2..50e3cb65b 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -97,7 +97,7 @@ Confirm: - `category` — `document-processing` (older `document-extraction` enum was renamed; current registry serves `document-processing`) - Input port: `input` -- Output ports: `success` and `error` (the `error` port is gated by `inputs.errorHandlingEnabled`; manifest sets `supportsErrorHandling: true`). Edges target these handle ids in `.flow` JSON; `handleType` is `output`. +- Output ports: `success` and `error` (the `error` port is gated by `inputs.errorHandlingEnabled`; manifest sets `supportsErrorHandling: true`). Edges target these handle IDs in `.flow` JSON; `handleType` is `output`. - `model.type` — `bpmn:ServiceTask`. `model.serviceType` — `IXP.Extraction`. The manifest's `model` is two fields only (`type`, `serviceType`) — no `context`, no `version`. Both are injected by the BPMN serializer at compile time. - `form.id` — `ixp-standalone-form`. Three sections: `ixp-model` (Configuration), `ixp-file-upload` (File input), `schema-definition` (Schema definition — a single custom field `inputs.model` rendered by the `ixp-model-taxonomy` component). - `inputDefinition.properties` — `model` (object), `modelName`, `projectName`, `projectId`, `versionTag`, `folderKey`, `folderName`, `fileRef`, `pageRange`, `attachmentConfig`, `guardrails`, `attachment`. `inputDefinition.required` — `["fileRef"]`. From 27dc47cab7f5a1bbfeb0b82a24a3731e7bdaa048 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Fri, 22 May 2026 13:16:56 +0100 Subject: [PATCH 7/7] address pr feedback --- .../references/author/references/plugins/ixp/impl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md index 50e3cb65b..6a29c8b50 100644 --- a/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md +++ b/skills/uipath-maestro-flow/references/author/references/plugins/ixp/impl.md @@ -84,7 +84,7 @@ Rules for the listing path: - **Do NOT mock.** If `Data: []`, answer directly: no IxP models are published on this tenant. The `core.logic.mock` fallback is for build-time planning, not for listing-time Q&A. - **Do NOT log in for the user.** If `uip login status` shows logged-out, tell the user to run `uip login` and stop — listing without auth returns OOTB-only results and is misleading. - **Do NOT search by `"runtime"`, `"document extractor"`, `"extractor"`, or `"IXP"` (uppercase).** These return empty results or agent-tool variants — not extraction nodes. Use `"uipath.ixp"` (lowercase) only. -- **Do NOT use `uip maestro flow process list` or any Orchestrator folder iteration.** `flow process list` enumerates *deployed flow process instances* (with `--folder-key`) — that is a different question. Listing published IxP models always goes through `registry search "uipath.ixp"`. +- **Do NOT use `uip maestro flow process list` or any Orchestrator folder iteration.** `flow process list` enumerates *deployed flow process instances* (with `--folder-key`), not published models. Listing published IxP models always goes through `registry search "uipath.ixp"`. - **Do NOT guess `uip maestro flow list-*` or `uip maestro ixp list-*` subcommands.** None exist. The CLI returns `unknown command 'list-...'` and there is no fallback path to pursue. ## Registry Validation @@ -119,7 +119,7 @@ The IxP node instance is **derived from the registry response**, not authored fr Run this once and source every field below from the response: ```bash -uip maestro flow registry get "" --output json > /tmp/ixp-def.json +uip maestro flow registry get "" --output json > .json ``` Then assemble the instance by copying these paths verbatim: @@ -153,7 +153,7 @@ If you find yourself typing any of those five field names while authoring an IxP { "id": "extractInvoiceFields", "type": "uipath.ixp.invoice-model.shared-invoice-model", - "typeVersion": "1.0.0", + "typeVersion": "", "display": { "label": "Extract Invoice Fields" }, "inputs": { "model": {