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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/test-helpers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ jobs:
- name: Run ScriptTask contract guard
run: pytest tests/scripts/test_bpmn_script_task_contract.py -v

bpmn-metadata-refresh-contract-guard:
runs-on: ubuntu-latest
name: maestro-bpmn metadata refresh contract guard
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install pytest
run: pip install pytest

- name: Run metadata refresh contract guard
run: pytest tests/scripts/test_bpmn_metadata_refresh_contract.py -v

catalog-build-guards:
runs-on: ubuntu-latest
name: catalog build integrity guards
Expand Down
27 changes: 26 additions & 1 deletion skills/uipath-maestro-bpmn/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Two halves make a valid Maestro `.bpmn`:

## Workflow

Work the four steps quickly, but keep the path matched to the user's ask. Treat
Work the five steps quickly, but keep the path matched to the user's ask. Treat
requests to discover before authoring, save raw registry JSON/evidence, or "do
not author yet" as discovery-only even if they describe an eventual BPMN. In
that mode, immediately create `registry-evidence/`, but run only the discovery
Expand Down Expand Up @@ -260,6 +260,31 @@ For registry-evidence-only tasks, be command-first and time-boxed:
structural rules, the installed CLI predates them — update it (see
[references/cli-conventions.md](references/cli-conventions.md)). See
[references/structural-bpmn.md#validation](references/structural-bpmn.md#validation).
5. **Refresh derived metadata when package-ready output is required.** After
source validation passes, regenerate the four CLI-owned package files from
the authored BPMN:

```bash
uip maestro bpmn \
refresh <project-path> --output json
```

This is the authoritative local source-to-derived-state boundary for
`entry-points.json`, `bindings_v2.json`, `operate.json`, and
`package-descriptor.json`. It is offline and provider-neutral: it does not
log in, discover tenant resources, invoke a connector, or replace
`uip solution resources refresh`. It derives entry points and supported V1
Connection declarations from identities already authored into the BPMN,
deduplicates shared connection resources, preserves a stable project ID,
and writes the generated set atomically. A second unchanged run is
idempotent.

Treat a nonzero result as a source/precondition failure. Fix the BPMN or
`project.uiproj`, rerun validation, and refresh again; never repair the four
generated JSON files by hand. If the installed CLI does not expose
`maestro bpmn refresh`, report package generation as blocked rather than
inventing metadata. Refresh is needed only for a package-ready, upload,
debug, publish, or deploy deliverable—not for a source-only draft.

## Operate and diagnose

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ under different solution resource keys.

These root bindings are source model state. `bindings_v2.json`,
`entry-points.json`, `operate.json`, and `package-descriptor.json` are generated
package state: never hand-create or edit them.
package state: never hand-create or edit them. After source validation, run
the BPMN `refresh` command to derive the V1 Connection resources and the rest
of the package metadata from the BPMN.

## 5. Serialize request inputs from the described schema

Expand Down
6 changes: 4 additions & 2 deletions skills/uipath-maestro-bpmn/references/operate/CAPABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ These actions may contact UiPath services or external systems.
migrate, and cursor movement require a clear user decision for that action.
3. **Validate before operate** - do not upload, publish, debug, or run until Author validation is complete or the user
explicitly accepts known draft warnings.
4. **Refresh or regenerate package metadata before cloud actions** - stale `bindings_v2.json`, `entry-points.json`,
`operate.json`, or `package-descriptor.json` can break import or runtime even when the BPMN source is correct.
4. **Refresh package metadata before cloud actions** - after source validation,
run the local BPMN `refresh` command; stale `bindings_v2.json`,
`entry-points.json`, `operate.json`, or `package-descriptor.json` can break
import or runtime even when the BPMN source is correct.
5. **Keep source and package ownership clear** - fix process structure, variables, mappings, events, and documented non-IS
extensions in `.bpmn`; rerun CLI generation/enrichment for generated package JSON and Integration Service metadata.
6. **Default publish wording to Studio Web upload unless the user explicitly asks for Orchestrator deployment** - keep deploy semantics explicit.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ Before upload, publish, deploy, or debug:
and package consistency.
3. Confirm Integration Service enrichment is complete for executable connector elements.
If enrichment tooling is unavailable, keep the project as a draft and do not operate it as executable.
4. Regenerate or refresh package metadata with the supported CLI path.
4. Regenerate package metadata from the validated BPMN source:

```bash
uip maestro bpmn \
refresh <project-path> --output json
```

Treat `bindings_v2.json`, `entry-points.json`, `operate.json`, and `package-descriptor.json` as derived unless a
CLI contract says otherwise. Use
[local-metadata-regeneration-guide.md](../../shared/local-metadata-regeneration-guide.md) for the local drift
Expand Down Expand Up @@ -46,8 +52,9 @@ uip maestro bpmn pack <project-path> <OutputDir> --output json

Use `--name` and `--version` only when the user provides a public-safe package identity.
Report the package path and package identity returned by the CLI.
If packing changes generated files, explain whether the change came from BPMN source, CLI enrichment,
or package generation.
Pack should consume the refreshed files rather than own their generation. If
the generated files changed, attribute that change to the preceding BPMN
refresh or enrichment step, not to hand-authored package JSON.

## Studio Web upload

Expand Down Expand Up @@ -95,7 +102,8 @@ When Studio Web import or packaging fails, inspect the generated package files a
- `package-descriptor.json` for content manifest entries under `content/`.

If the mismatch comes from process modeling, fix `.bpmn` in Author.
If it comes from connector metadata or generated resources, rerun CLI enrichment/generation.
If it comes from connector metadata or generated resources, fix or re-enrich
the BPMN source and rerun BPMN refresh.
Do not hand-patch generated files as the primary fix.

## Failure handling
Expand All @@ -106,7 +114,8 @@ If package or upload fails:
- Check generated package files against [shared/project-layout.md](../../shared/project-layout.md).
- Correlate import errors to BPMN diagrams, start events, entry point IDs, bindings, and package descriptor entries.
- Return to Author for BPMN/source fixes.
- Rerun CLI enrichment/generation for CLI-owned Integration Service or generated-file issues.
- Fix or re-enrich the BPMN source and rerun BPMN refresh for CLI-owned
Integration Service or generated-file issues.
- Use Diagnose only after a process has actually run or faulted in the runtime.

## Anti-patterns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Use this guide when BPMN source changed and local package metadata must be refreshed or verified before packaging, upload, debug, publish, or deploy.

The BPMN `refresh` command is the authoritative local source-to-derived-state
boundary. It requires exactly one project-root `.bpmn` file
and atomically regenerates the complete package metadata set. The command is
offline and provider-neutral: it does not log in, discover a tenant, invoke a
connector, or resolve an account. It consumes only identities already authored
into the supported BPMN contract.

## Ownership

- `.bpmn` is the source of record for process structure, root variables, root bindings, entry point IDs, mappings, diagrams, and documented non-Integration-Service UiPath XML.
Expand Down Expand Up @@ -41,46 +48,66 @@ synthetic package.

Local regeneration reads:

- Root-level `bpmn:startEvent` elements with `uipath:entryPointId`.
- Root manual `bpmn:startEvent` elements with `uipath:entryPointId`.
- Root `uipath:variables` for entry point input/output schemas.
- Root `uipath:bindings` for package resources.
- Enriched `uipath:activity` and `uipath:event` payloads for `Intsvc.*` context fields, request payloads, output mappings, and schemas.
- The project/start-event path from `operate.json.main` or the selected BPMN
file and root start event.
file and root manual start event.

Do not derive metadata from stale package files first. Use existing generated files only as a drift comparison or as CLI-owned enrichment input when the CLI explicitly supports that workflow.

## Safe Local Workflow

1. Edit `.bpmn` first.
2. Run local validation for XML, diagrams, entry point IDs, variables, mappings, binding references, and package metadata drift.
3. Before running `pack`, verify the project directory contains the full local
metadata set: `project.uiproj`, `operate.json`, `entry-points.json`,
`bindings_v2.json`, and `package-descriptor.json`. The pack command
consumes these files; it does not synthesize a missing package descriptor.
4. If generated package JSON is stale, regenerate it with the supported local
CLI path. Do not substitute hand-written package metadata. For package-shape
verification, use the local pack command and request JSON output when
parsing command results:
3. After validation succeeds, regenerate derived metadata:

```bash
uip maestro bpmn pack <project-path> <OutputDir> --output json
uip maestro bpmn \
refresh <project-path> --output json
```

5. Inspect the package or generated content for:
- `entry-points.json` entries matching root start events and schemas.
A successful response reports the BPMN file, main path, project ID, entry
point count, Connection-resource count, files written, and files unchanged.
The command derives supported V1 Connection resources from root bindings and
`Intsvc.*` activity context, and deduplicates activities that intentionally
share one `resourceKey`. This is connector-provider-neutral; the same source
contract applies regardless of which provider owns the connection.
4. Verify the project now contains the full local metadata set:
`project.uiproj`, `operate.json`, `entry-points.json`, `bindings_v2.json`,
and `package-descriptor.json`. Run refresh a second time only when checking
idempotence; unchanged source must leave all four generated files unchanged.
5. Inspect the generated content for:
- `entry-points.json` entries matching root manual start events and schemas.
- `bindings_v2.json` resources matching root bindings and enriched connector metadata.
- `operate.json` pointing at the intended BPMN file with `ProcessOrchestration` content type.
- `package-descriptor.json` root `files` mappings for the BPMN file and generated JSON.
6. If the installed CLI cannot regenerate a needed file in place, keep the
generated file stale only as a known blocker and report the exact unsupported
step. A source-only project is not package-ready.
6. For package-shape verification, run `pack` only after refresh. Pack consumes
the generated files; it does not synthesize a missing package descriptor:

```bash
uip maestro bpmn pack <project-path> <OutputDir> --output json
```

If refresh fails, the atomic write contract leaves the prior four-file set
unchanged. Fix the reported source or project precondition and run it again; do
not patch generated JSON around the failure. The current contract requires
exactly one project-root `.bpmn` file, one or more root processes, and at least
one root manual start event overall. Each root manual start event must carry
exactly one valid GUID `uipath:entryPointId`; refresh generates one
`entry-points.json` entry for each such start event. It rejects unsupported
binding-resource kinds instead of silently dropping them. If the installed CLI
does not expose this command, keep any stale generated files only as known
comparison evidence and report package generation as blocked. A source-only
project is not package-ready.

Packaging is local and authoring-safe. Upload, publish, deploy, debug, and run are cloud or runtime actions and still require explicit user consent.

## Entry Point Rules

For each root start event with `uipath:entryPointId`, generated `entry-points.json` must include:
For each root manual start event, require exactly one valid GUID
`uipath:entryPointId`. Its generated `entry-points.json` entry must include:

- `uniqueId` equal to the `uipath:entryPointId` value.
- `filePath` equal to `/content/<bpmn-file>#<start-event-id>`.
Expand Down Expand Up @@ -140,6 +167,6 @@ If enrichment is unavailable, leave the BPMN element as draft intent. Do not han
## Drift Handling

- If `entry-points.json` differs from root variables or start event IDs, fix the BPMN source first, then regenerate.
- If `bindings_v2.json` differs from root bindings or `Intsvc.*` context references, rerun enrichment/generation.
- If `operate.json` or `package-descriptor.json` points at the wrong BPMN file, refresh package metadata through the CLI path.
- If `bindings_v2.json` differs from root bindings or `Intsvc.*` context references, fix or re-enrich the BPMN source, then run the BPMN refresh command again.
- If `operate.json` or `package-descriptor.json` points at the wrong BPMN file, rerun BPMN refresh instead of editing either file.
- Do not commit private IDs, tenant URLs, connection IDs, folder keys, or copied customer payloads while resolving drift.
17 changes: 13 additions & 4 deletions skills/uipath-maestro-bpmn/references/shared/project-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,19 @@ and leaves it untouched — the project still lands in

Treat these JSON files as derived unless a CLI contract explicitly identifies a field as user-authored. For source fixes, edit BPMN or rerun CLI enrichment rather than patching generated output by hand.

Local packaging requires the generated metadata set to exist. In particular,
`uip maestro bpmn pack <project-path> <OutputDir> --output json` consumes
`package-descriptor.json`; it does not create a missing descriptor from only
the BPMN and `project.uiproj`.
After source validation, generate the complete set with:

```bash
uip maestro bpmn \
refresh <project-path> --output json
```

Refresh is the offline, provider-neutral source-to-derived boundary. It
regenerates all four files as one atomic set from the authored BPMN and
`project.uiproj`; it does not discover or import tenant resources. Local
packaging then consumes that generated set. In particular,
`uip maestro bpmn pack <project-path> <OutputDir> --output json` does not create
a missing descriptor from only the BPMN and `project.uiproj`.

For the regeneration and drift-check contract, see [local-metadata-regeneration-guide.md](local-metadata-regeneration-guide.md).

Expand Down
106 changes: 106 additions & 0 deletions tests/scripts/test_bpmn_metadata_refresh_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Guard the BPMN source-to-derived metadata refresh contract."""

from __future__ import annotations

import re
from pathlib import Path


ROOT = Path(__file__).resolve().parents[2]
SKILL_ROOT = ROOT / "skills" / "uipath-maestro-bpmn"
SKILL = SKILL_ROOT / "SKILL.md"
METADATA_GUIDE = (
SKILL_ROOT / "references" / "shared" / "local-metadata-regeneration-guide.md"
)
PROJECT_LAYOUT = SKILL_ROOT / "references" / "shared" / "project-layout.md"
ACTIVITY_GUIDE = (
SKILL_ROOT / "references" / "integration-service-activity-authoring-guide.md"
)
SHIP = SKILL_ROOT / "references" / "operate" / "references" / "ship.md"

REFRESH = "uip maestro bpmn refresh <project-path> --output json"
VALIDATE = "uip maestro bpmn validate <file.bpmn> --output json"
PACK = "uip maestro bpmn pack <project-path> <OutputDir> --output json"
GENERATED = (
"entry-points.json",
"bindings_v2.json",
"operate.json",
"package-descriptor.json",
)


def _text(path: Path) -> str:
return path.read_text(encoding="utf-8")


def _prose(text: str) -> str:
return " ".join(text.split())


def _commands(text: str) -> str:
return re.sub(r"\\\s*\n\s*", "", text)


def test_skill_orders_validate_before_refresh_and_scopes_it_to_packages() -> None:
text = _text(SKILL)
commands = _commands(text)

assert "Work the five steps" in text
assert commands.index(VALIDATE) < commands.index(REFRESH)
section = text.split(
"5. **Refresh derived metadata when package-ready output is required.**",
maxsplit=1,
)[1].split("## Operate and diagnose", maxsplit=1)[0]
prose = _prose(section)
assert all(name in section for name in GENERATED)
assert "offline and provider-neutral" in prose
assert "does not log in" in prose
assert "does not expose" in prose
assert "never repair the four generated JSON files by hand" in prose


def test_metadata_guide_makes_refresh_the_atomic_source_boundary() -> None:
text = _text(METADATA_GUIDE)
commands = _commands(text)
prose = _prose(text)

assert REFRESH in commands
assert commands.index("Run local validation") < commands.index(REFRESH)
assert commands.index(REFRESH) < commands.index(PACK)
assert all(name in text for name in GENERATED)
assert "authoritative local" in prose
assert "offline and provider-neutral" in prose
assert "atomically regenerates" in prose
assert "atomic write contract leaves the prior four-file set unchanged" in prose
assert "deduplicates activities" in prose
assert "exactly one project-root `.bpmn` file" in prose
assert "one or more root processes" in prose
assert "at least one root manual start event" in prose
assert "exactly one valid GUID `uipath:entryPointId`" in prose
assert "one `entry-points.json` entry for each such start event" in prose


def test_lifecycle_guides_delegate_generation_to_refresh_not_pack() -> None:
layout = _text(PROJECT_LAYOUT)
activity = _text(ACTIVITY_GUIDE)
ship = _text(SHIP)
layout_commands = _commands(layout)
ship_commands = _commands(ship)
layout_prose = _prose(layout)
activity_prose = _prose(activity)
ship_prose = _prose(ship)

assert REFRESH in layout_commands
assert "source-to-derived boundary" in layout_prose
assert "does not discover or import tenant resources" in layout_prose
assert layout_commands.index(REFRESH) < layout_commands.index(
"uip maestro bpmn pack <project-path> <OutputDir> --output json"
)

assert "the BPMN `refresh` command" in activity
assert all(name in activity for name in GENERATED)
assert "never hand-create or edit them" in activity_prose

assert REFRESH in ship_commands
assert ship_commands.index(REFRESH) < ship_commands.index(PACK)
assert "Pack should consume the refreshed files" in ship_prose
Loading