feat(maestro): add Business Apps CRUD service - #671
Conversation
Review summaryOne finding posted as an inline comment. tests/integration/utils/cleanup.ts — incomplete cleanupAllTestResources() update The PR adds businessApps to ResourceRegistry and its initial state but does not update cleanupAllTestResources() to iterate resourceRegistry.businessApps or reset it to [] in the Clear registry block. This means registerResource('businessApps', ...) calls in the integration test are effectively dead code for emergency cleanup, and orphaned apps created before a mid-run crash won't be deleted by the global handler. Everything else looks correct — transform pipeline, pagination wiring, bound-method delegation, test coverage, JSDoc, and docs (oauth-scopes, pagination quick-ref, mkdocs nav) all follow conventions. |
Addresses review feedback on #671: - cleanupAllTestResources() now deletes registered business apps and clears the registry slot; registerResource('businessApps', ...) was otherwise inert. - Removes the tenant-level APPS.* permission note from docs/oauth-scopes.md — that page documents OAuth scopes, not RBAC permissions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
1a8a788 to
b1fee03
Compare
|
|
@vnaren23 — your assumption is right, and this is now done in 72cb174. Apologies for the delay; I'd been working from the line-comment threads and missed this one, since it's a top-level review body rather than an inline comment. Confirming the premise: the backing endpoints merged in PO.BpmnEngine#4631 on Aug 10 and have only rolled out to alpha. Not available to customers, so preview is the correct status. Following the pattern from #328 and the existing
On "mention the same on docs": the admonition is the docs mention — it renders as a warning callout on the generated API page. I verified that rather than assuming, by regenerating and checking the output: Two things I deliberately did not do, so they're a conscious choice rather than an oversight:
Generated by Claude Code |
Review summaryOne new finding posted as an inline comment. src/models/maestro/business-apps.models.ts line 218 — missing data.id guard in createBusinessAppMethods The convention requires bound method factories to validate required entity fields before delegating. createBusinessAppMethods builds update and delete closures without checking data.id, so a malformed API response with an empty id would produce a confusing ValidationError from the service layer rather than a clear error at the attachment boundary. A one-line guard at the top of the factory fixes this, along with a companion model test. Everything else looks correct — transform pipeline (rename-only, no pascalToCamelCaseKeys since PIMS is already camelCase), pagination wiring (token pagination with BUSINESS_APP_PAGINATION and PROCESS_INSTANCE_TOKEN_PARAMS reuse), endpoint constants (method-agnostic COLLECTION and BY_ID), JSDoc on BusinessAppsServiceModel (complete with examples, params, returns), docs (oauth-scopes.md, pagination.md, mkdocs.yml), subpath export wiring (package.json + rollup.config.js), and test coverage (service, model, and integration tests all follow conventions). |
|
✅ No issues found. Checked for bugs and CLAUDE.md compliance. |
|
Two CI notes, neither of which is a defect in this diff — plus one finding worth a maintainer's decision.
|
|
✅ No issues found. Checked for bugs and CLAUDE.md compliance. |
|
Following up on the Maestro integration-test exclusion I raised above: going with option 1 — filed as #673, and this PR leaves The issue records the provenance (c7a56c9, #620), the six affected files, the Practical consequence for this PR, unchanged and worth keeping visible at review time: Generated by Claude Code |
Review summaryOne new finding posted as an inline comment. src/models/maestro/business-apps.models.ts lines 200-206 —
The PR description flags this as "worth a reviewer's opinion" with no explicit sign-off yet. The recommended shape is |
|
✅ No issues found. Checked for bugs and CLAUDE.md compliance. |
Review summaryOne new finding posted as an inline comment. tests/integration/shared/maestro/business-apps.integration.test.ts line 134 — missing The JSDoc |
|
✅ No issues found. Checked for bugs and CLAUDE.md compliance. |
|
@kittyyueli Curently maestro integration tests are skipped, can you run these tests locally to confirm they are passing |
|
Update on the This pattern reads as transient instability in the shared live test tenant/environment (intermittent 30s timeouts across unrelated services), not a regression in any PR. I've triggered another re-run of the failed jobs; if it keeps recurring I won't keep spinning re-runs indefinitely since it's clearly not caused by this change — flagging that here so it doesn't look like a stalled/ignored red check. Generated by Claude Code |
|
Root cause confirmed on this latest run: the Across 4 re-runs the failures have moved between different tests and files (Data Fabric I'm standing down from further automatic re-runs here — a 504 from the live tenant isn't something I can fix from this PR, and re-running mechanically into the same backend issue isn't productive. This PR's own checks (lint, typecheck, unit tests, build, jsdoc-validation, business-apps integration suite) are all green; Generated by Claude Code |
Onboards the PIMS v1/business-apps endpoints (PO.BpmnEngine #4631) as a new modular BusinessApps service: create, getAll, getById, updateById, deleteById. - Token pagination over businessApps/nextPage, reusing the pageSize/nextPage params - Renames the wire audit fields (createdTimeUtc, modifiedTimeUtc, modifiedBy) onto the SDK's *Time / lastModified* naming - update and delete bound onto each returned app - Unit, model and integration tests; oauth-scopes, pagination and mkdocs entries Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
Addresses review feedback on #671: - cleanupAllTestResources() now deletes registered business apps and clears the registry slot; registerResource('businessApps', ...) was otherwise inert. - Removes the tenant-level APPS.* permission note from docs/oauth-scopes.md — that page documents OAuth scopes, not RBAC permissions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
updateById exercised only the id and processKeys guards; name and description were covered on create alone. Each method owns its own validation coverage, so a divergence in assertWritableFields for one caller is caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
The backend endpoints rolled out to alpha only and are not yet available to customers, so the SDK surface is flagged as preview: @experimental plus the warning admonition on BusinessAppsServiceModel and all five methods, matching AgentMemory, Functions and Governance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
update and delete closed over data.id without checking it, so a malformed payload forwarded an empty id to the service and surfaced a ValidationError naming businessAppId rather than the entity. Both methods are async so the guard rejects instead of throwing synchronously out of a promise-typed method. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
nextCursor is optional on PaginatedResponse and cursor is optional on PaginationOptions, so an undefined cursor type-checks and re-requests the first page — the unguarded loop would append page one forever. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
…rimental The service model and its methods already carried @experimental; the class and the module docblock did not, so the preview status was invisible on hover at the import site. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
Same fix as 0229b41, which corrected only the JSDoc example and left the integration test's loop unguarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
…erimental getAll builds its own transformFn closure, so getById's completeness test did not cover it — breaking a field-map entry now fails both. BusinessAppMethods is a publicly exported interface with its own TypeDoc page, so it carries the preview warning alongside the service model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
PIMS made description optional in PO.BpmnEngine#4818 (MST-13833): it is string? on the request and response records, moved into the optional tail, and the validator's required guard is gone — blank now collapses to null. Mirrors that here. description moves out of the positional list into the options bag on create, updateById and the bound update, and becomes string | null on the response. The SDK-side required guard is dropped so it cannot reject a payload the server accepts.
… mutation support Every mutation in this suite 403s with the token this suite currently authenticates as: PIMS accepts it, but TenantPermissionHandler rejects create/update/delete on the tenant-scoped ORCHESTRATOR.APPS.* check. Reads alone can't exercise the suite (create seeds every fixture), so the whole file is unrunnable until the credential holds that permission or app tokens gain mutation support here. Test bodies are unchanged so re-enabling is a one-line describe.skip removal once that lands.
Neither carried @PARAM docs, which the new jsdoc-validation CI check (typedoc.validation.json, treatValidationWarningsAsErrors) requires for every exported Method, CallSignature and Parameter. Matches the existing CaseInstanceMethods.close / createCaseInstanceWithMethods pattern.
3d73513 to
6a0d8a2
Compare
Review summaryOne new finding posted as an inline comment. tests/integration/shared/maestro/business-apps.integration.test.ts line 24 — The convention permits |
|
Rebased onto
Generated by Claude Code |
Case app v2 hasn't shipped yet, so tag every Business Apps declaration (service class, module, ServiceModel/Methods interfaces and their methods, raw types, and the factory function) as @internal to exclude them from generated docs. Drops the now-inapplicable oauth-scopes.md, pagination.md, and mkdocs.yml nav entries for a fully internal service. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT
|
Per Sarath's request: tagged the whole Business Apps API surface Verified locally with The service stays fully importable via Generated by Claude Code |
|
✅ No issues found. Checked for bugs and CLAUDE.md compliance. |
|
Generated by Claude Code |
|
|
Please update PR description, mention these are internal methods |
|
Done — updated the description: added a callout under Summary explaining the whole service is Generated by Claude Code |



Summary
Onboards the PIMS
v1/business-appsendpoints from PO.BpmnEngine #4631 (MST-13159) as a new modularBusinessAppsservice.A business app is the tenant-level definition behind a workspace in Maestro — name, description, icon, color, and the Orchestrator process keys it surfaces. Tenant-scoped, so no folder key is involved.
This entire service is
@internal. Case app v2 hasn't shipped yet, so every declaration in this PR —BusinessAppsService,BusinessAppsServiceModel/BusinessAppMethodsand their methods, the module docblock, the raw types (RawBusinessAppGetResponse,BusinessAppCreateOptions,BusinessAppUpdateOptions), the composedBusinessAppGetResponsetype, andcreateBusinessAppWithMethods— is tagged@internaland excluded from generated docs (excludeInternal: true). The service is still fully importable via@uipath/uipath-typescript/business-appsfor internal use;@internalonly hides it from the public API reference, it doesn't restrict usage. It'll come off@internalonce case app v2 actually ships.Companion whitelist PR: UiPath/apps-dev-tools#148 — merged.
API
Import path
@uipath/uipath-typescript/business-apps.create(name, processKeys, options?)POST /v1/business-appsBusinessAppGetResponsegetAll(options?)GET /v1/business-appsBusinessAppGetResponsegetById(businessAppId)GET /v1/business-apps/{id}BusinessAppGetResponseupdateById(businessAppId, name, processKeys, options?)PUT /v1/business-apps/{id}BusinessAppGetResponsedeleteById(businessAppId)DELETE /v1/business-apps/{id}voidupdateanddeleteare bound onto each returned app.getAll,getByIdandcreateare not — they are service-level entry points.Design notes
JsonNamingPolicy.CamelCase, so nopascalToCamelCaseKeys().BusinessAppMaprenames the audit fields onto SDK naming:createdTimeUtc→createdTime,modifiedTimeUtc→lastModifiedTime,modifiedBy→lastModifiedBy. Request bodies need no outbound transform — no body field is renamed.description,iconandcolorarestring | null, not optional. PIMS configures noDefaultIgnoreCondition, so all three keys are always present on a response and may be null. Typing them optional would have been wrong.businessApps/nextPage. NewBUSINESS_APP_PAGINATIONfor the response shape; request params reusePROCESS_INSTANCE_TOKEN_PARAMS, which is already the samepageSize+nextPagepair (same reuse precedent asAGENTS_INCIDENTS_PAGINATION).jumpToPageis unsupported, as for every token-paginated list.COLLECTION,BY_ID) rather than one per verb — the collection URL is shared by GET and POST, and the id URL by GET/PUT/DELETE, and the conventions forbid duplicate constants that differ only by HTTP method.BusinessAppApiResponse(internal) describes the wire shape;RawBusinessAppGetResponse(public) is the post-rename shape users compose withBusinessAppMethods. Defined independently rather than derived from one another.name, non-emptyprocessKeys, id on the by-id methods). Length, charset, hex-colour and name-uniqueness rules stay server-side so the SDK cannot drift from them.Parameter shape
descriptionis optional as of PO.BpmnEngine#4818 (MST-13833), so it sits in the options bag besideiconandcolorrather than in the positional list. That leavescreatewith 2 required parameters andupdateByIdwith 3, both under the conventions' ≤3 threshold, so they stay positional and no{Entity}{Operation}Requestobject is needed. The boundupdate(name, processKeys, options?)has 2 required after the entity supplies its own id.This also retires the
name/descriptionswappability concern raised earlier in review — the two are no longer adjacent same-typed positional parameters.Testing
tests/unit/services/maestro/business-apps.test.ts) — success and error paths for all five methods, transform completeness forgetAllandgetByIdseparately (each has its owntransformFnclosure), nulldescription/icon/colorpreservation, omitted optional fields absent from the write body,pageSize/nextPagequery wiring,jumpToPagerejection, and pre-flight validation asserting no HTTP call is made.tests/unit/models/maestro/business-apps.test.ts) — bound-method delegation, the missing-id guard on each bound method, and that two apps built from one list each bind to their own id.tests/integration/shared/maestro/business-apps.integration.test.ts) — written in full (CRUD round-trip, create with no optional fields at all, case-insensitive duplicate-name conflict, full-replace clearing every omitted optional field, paging, both bound methods) but currentlydescribe.skip'd — see Integration test status below.Assertions were mutation-checked rather than trusted for being green. Breaking a
BusinessAppMapentry fails both transform-completeness tests; injecting a defaulteddescriptioninto the create body fails the omitted-description test.npm run typecheck,npm run lint,npm run test:unit(2652 passing) andnpm run buildall pass.Docs
Since the whole service is
@internal, it gets none of the usual public-doc additions — nodocs/oauth-scopes.mdsection, nodocs/pagination.mdrow, nomkdocs.ymlnav entry, and no generateddocs/api/**pages (verified vianpm run docs:api: everyBusinessApp*page — interfaces, the composed type, andcreateBusinessAppWithMethods— is excluded). Same treatment as the other fully-@internalservices in this repo (e.g.DataFabricRoleService,DataFabricDirectoryService).Integration test status
The Maestro integration tests now execute in CI, since #674 removed the
tests/integration/shared/maestro/**exclusion. Every mutation in this suite (create,updateById,deleteById, both bound methods) 403'd deterministically against the live tenant.Traced to the actual cause rather than assumed:
BusinessAppsControllergates those methods onTenantPermissionHandler, which callsIAuthzClient.CheckForPermissionInTenantAsync(tenantId, "ORCHESTRATOR.APPS.*", ...)— a real tenant-scoped RBAC check, not a route or payload bug. The credential this suite currently authenticates with doesn't hold that permission at tenant scope, and per discussion, app tokens may not support mutations here yet regardless of permission grant.Since only
createwas reachable — every test seeds a fixture through it first —getAll/getById/updateById/deleteByIdwere never actually exercised against the live API either.Skipped for now (
describe.skip.each, matching the existinginsightsrtm_/OAuth-only precedent in this repo — e.g.agents.integration.test.ts,memory.integration.test.ts) rather than left red or deleted. Test bodies are untouched, so re-enabling is a one-line change once the credential holds the permission or app-token mutation support lands.Two other Maestro files that #674 enabled (
case-instances,process-instances,process-incidents) also failed for unrelated reasons that varied between runs — untouched here, not from this diff.No version bump here — that ships separately per the release workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JWFuFCG3znzxGTXgMfQkRT