From fe8644c9b9986e8b43fe4f17fc1f9099a91c1a60 Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Thu, 24 Sep 2026 23:23:17 +0100 Subject: [PATCH 1/6] Use read-only Business Applications discovery Align Work IQ and preview plugin guidance with the request-visible search_paths schema, direct structural fetch paths, and automatic provider discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/workiq-preview/SKILL.md | 3 + .../references/business-applications.md | 86 +++++++++++++++++++ .../references/do-action-work-iq.md | 6 +- .../references/search-paths-work-iq.md | 13 ++- .../references/troubleshooting.md | 10 ++- plugins/workiq/skills/workiq/SKILL.md | 26 +++--- .../references/business-applications.md | 42 +++++---- .../workiq/references/do-action-work-iq.md | 5 ++ .../workiq/references/search-paths-work-iq.md | 47 +++++++--- .../workiq/references/troubleshooting.md | 11 ++- tests/workiq-guidance/contract.mjs | 3 +- tests/workiq-guidance/doc-lint.mjs | 31 ++++--- tests/workiq-guidance/static.test.mjs | 84 ++++++++++++++++++ 13 files changed, 309 insertions(+), 58 deletions(-) create mode 100644 plugins/workiq-preview/skills/workiq-preview/references/business-applications.md diff --git a/plugins/workiq-preview/skills/workiq-preview/SKILL.md b/plugins/workiq-preview/skills/workiq-preview/SKILL.md index 45153b4..07489b6 100644 --- a/plugins/workiq-preview/skills/workiq-preview/SKILL.md +++ b/plugins/workiq-preview/skills/workiq-preview/SKILL.md @@ -32,6 +32,7 @@ before calling. Never guess aliases or derive prefixes from a skill folder. | Call an OData function (delta, reminderView, named-file search) | `call_function` | | Download file or attachment bytes | `fetch_blob` | | Discover entity paths / inspect operation fields and body shape | `search_paths` / `get_schema` | +| Discover or use structured records and workflows in CRM, ERP, or Power Apps | `search_paths` for unknown paths; then the exact entity or action tool — read [Business Applications](references/business-applications.md) first | Semantic does not automatically mean `retrieve` or `ask`: exact entity URLs, bounded listings, and known workflows stay on entity tools, with local synthesis. @@ -95,6 +96,7 @@ for exact IDs, attribution, and same-agent `conversationId` continuation. | People | `/me`, `/users/{id}`, `/me/manager`, `/me/contacts` | profile, org chart, personal contacts; directory and contact IDs are not interchangeable | | Files | `/me/drive`, `/drives/{id}`, `/sites/{id}` | metadata via entity tools; bytes via `fetch_blob`; named OneDrive search via `call_function` | | Planner | `/me/planner/plans`, `/planner/tasks` | list/create/update/complete/delete | +| Business Applications | `/businessapps/environments/` and discovered `/businessapps/...` paths | direct `fetch` for environment inventory; `search_paths` for unknown records, apps, skills, APIs, and operations | | Change tracking | `/me/mailFolders/inbox/messages/delta`, `/me/calendarView/delta`, `/me/contacts/delta` | `call_function` only, never `fetch` | ## Required Workflow Order @@ -161,6 +163,7 @@ inline wording or a new message for a requested persisted reply. | Copy/move/rename/delete files; upload sessions | [Files](references/files-work-iq.md) | | Cancel/delete/reschedule/forward meetings; reminders/free-busy | [Calendar](references/calendar-work-iq.md) | | Mail / Teams / Planner | [Mail](references/mail-work-iq.md) / [Teams](references/teams-work-iq.md) / [Tasks](references/tasks-work-iq.md) | +| CRM, ERP, Power Apps, Dataverse records and workflows | [Business Applications](references/business-applications.md) | | Reads, paging / binary downloads / delta and functions | [fetch](references/fetch-work-iq.md) / [fetch_blob](references/fetch-blob-work-iq.md) / [call_function](references/call-function-work-iq.md) | | Paths / schemas | [search_paths](references/search-paths-work-iq.md) / [get_schema](references/get-schema-work-iq.md) | | Create / update / delete / actions | [create_entity](references/create-entity-work-iq.md) / [update_entity](references/update-entity-work-iq.md) / [delete_entity](references/delete-entity-work-iq.md) / [do_action](references/do-action-work-iq.md) | diff --git a/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md new file mode 100644 index 0000000..cf02276 --- /dev/null +++ b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md @@ -0,0 +1,86 @@ +# Business Applications + +Use WorkIQ entity tools with paths under `/businessapps/` for structured CRM, +ERP, Power Apps, and Dataverse environments, records, apps, skills, APIs, and +operations. This resource family is distinct from Microsoft Graph application +registrations. Do not substitute another MCP server or invent REST paths. + +## Discovery and known inventory + +1. For an unknown record, workflow, app, skill, API, operation, or identifier, + call `search_paths` once with a focused natural-language description. Use the + required `query` or legacy `filter` advertised by the live schema and send + only accepted fields. Never try both interfaces after rejection. +2. For environment inventory, skip discovery and call `fetch` directly on + `/businessapps/environments/`. Use the exact returned environment IDs; never + assume a default environment. +3. Follow exact returned paths with `fetch`, `get_schema`, or the effect-correct + write/action tool. Do not guess path segments, IDs, names, or casing. +4. Before an unfamiliar write or action, call `get_schema` on the concrete + path with the matching operation type. Schema availability does not grant + authorization. + +Discovery is read-only: use `search_paths`, not `do_action` on +`/businessapps/me`. `do_action` remains appropriate for a discovered +environment SQL query, Custom API, app operation, in-app MCP tool, or delegated +environment work. + +Do not use `do_action` on `/businessapps/me` for discovery even if that route is +exposed; use `search_paths` instead. The old route is POST-shaped and can be +policy-denied before any grounded path is returned, ending the workflow before +`search_paths` or `fetch` can run. + +## Exact tool and path selection + +| Intent | Tool and path | +| --- | --- | +| List environments or identify the default | `fetch` `/businessapps/environments/` | +| List or inspect tables | `fetch` or `get_schema` on `/businessapps/environments/{environmentId}/tables[/]` | +| Read a record | `fetch` `/businessapps/environments/{environmentId}/tables/{tableName}/records/{recordId}` | +| Query environment data | `do_action` `/businessapps/environments/{environmentId}/query` with schema-defined `jsonBody`, including read-only `querytext` when required | +| Create a record | `create_entity` on `/businessapps/environments/{environmentId}/tables/{tableName}/records` | +| Update or delete a record | `update_entity` or `delete_entity` on the exact discovered record path | +| List or inspect apps | `fetch` `/businessapps/environments/{environmentId}/apps[/]` | +| List or inspect business skills | `fetch` `/businessapps/environments/{environmentId}/skills[/{skillName}]` | +| Run an app operation | `do_action` on the exact discovered `.../operations/{operationName}` path | +| Invoke a Custom API | `do_action` on the exact discovered `/businessapps/environments/{environmentId}/customapis/{apiName}` path | +| Delegate open-ended environment work | `do_action` `/businessapps/environments/{environmentId}/execute-work` with the schema-defined instruction and optional session ID | + +Environment SQL and Custom APIs with request bodies are actions, not OData +functions. Use `call_function` only for a function path returned by discovery. +Business Applications file routes are also distinct from Graph `fetch_blob` and +the unreleased Graph `upload_blob`; use only the operation returned by the +Business Applications schema. + +## Confirmation, policy, and failure boundaries + +Classify effects from the discovered operation contract. Apply the general +confirmation workflow before `create_entity`, `update_entity`, `delete_entity`, +or a mutating `do_action`. A read-only SQL query does not require mutation +confirmation merely because it uses `do_action`. + +WorkIQ may deny an action or mutation based on caller trust, tenant policy, or +resource privileges. This does not make read-only `search_paths` discovery or a +permitted structural `fetch` into mutations. On an explicit denial: + +- stop the affected workflow and report the observed diagnostic; +- do not retry through another path, tool, agent, endpoint, or plugin; +- do not assert that a particular tenant setting or consent change will fix it + unless the diagnostic says so; and +- do not claim success or substitute a different record, table, skill, or app. + +When the outcome of a mutation is ambiguous, do not replay it. Reconcile with a +supported safe read when available; otherwise report **outcome unknown**. + +## Grounding and capability matching + +- Preserve exact returned IDs and paths. Structural segments can be + case-insensitive, but opaque identifiers are not. +- For metadata with an unknown path, use `search_paths`, then fetch the returned + resource. For known structural inventory, use the direct `fetch` path. +- A top-level semantic retrieval may include Dataverse evidence, but it does not + replace authoritative path discovery for a requested Business Applications + record or operation. +- A named skill, app operation, or Custom API requires an exact discovered + capability match. Do not silently substitute a similar capability or use + `execute-work` as a fallback. diff --git a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md index 87e3421..a0091f1 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md @@ -2,8 +2,9 @@ Invoke a named WorkIQ action. An action can read, mutate, or create a resource; POST and the tool name alone do not establish its effects. Read-only free/busy, -structured search, and Business Applications discovery do not require mutation -confirmation just because they use this tool. +and structured search do not require mutation confirmation just because they +use this tool. Use `search_paths`, not `do_action`, for Business Applications +discovery. ## Parameters @@ -42,6 +43,7 @@ mutations even if they do not send a message. | Accept/decline, cancellation, forwarding, free/busy | [Calendar](calendar-work-iq.md) | | Drive-scoped copy and upload-session creation | [Files](files-work-iq.md) | | Read/unread, reactions, presence | [Teams](teams-work-iq.md) | +| Environment SQL, Custom APIs, app operations, and delegated environment work | [Business Applications](business-applications.md) | Use [create_entity](create-entity-work-iq.md) for collection creation such as a fresh draft; `createReply`/`createReplyAll`/`createForward` remain actions. diff --git a/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md index bcbda32..1da0090 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md @@ -12,9 +12,11 @@ Older catalogs may expose a regex `filter` instead. Inspect the actual advertise schema and send only its accepted fields; do not translate examples into guessed arguments or try both interfaces after rejection. -There is no basis for inventing `backend`, `source`, or `provider` selectors. -Nor does path discovery imply that the entire catalog is Graph-only: retain -exact returned resource families and their domain contracts. +There is no `backend`, `source`, or `provider` argument. WorkIQ automatically +searches every enabled catalog and provider available to the current tenant and +caller. Path discovery is not Graph-only: retain exact returned resource +families and their domain contracts, and do not claim an unavailable provider +is enabled. ## Workflow @@ -34,6 +36,11 @@ operation, not just common examples. Inspect an available saved capped result before claiming coverage; if the response is truncated, qualify completeness. Do not invent paths absent from the result. +For structured CRM, ERP, Power Apps, or Dataverse resources and workflows, read +[Business Applications](business-applications.md). Use natural-language +`search_paths` for unknown paths, but use direct +`fetch("/businessapps/environments/")` for environment inventory. + ## Examples for the `query` catalog ```json diff --git a/plugins/workiq-preview/skills/workiq-preview/references/troubleshooting.md b/plugins/workiq-preview/skills/workiq-preview/references/troubleshooting.md index 1db639c..12b41bf 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/troubleshooting.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/troubleshooting.md @@ -6,8 +6,8 @@ may impose tighter bounds; a happy-path call budget never overrides safety. ## Classify effects before recovery Classify the operation by its documented effects, not the tool name or HTTP verb. -`do_action` can perform read-only `getSchedule`, `/search/query`, or Business -Applications discovery. Persisting a draft, changing read state or presence, +`do_action` can perform read-only `getSchedule` or `/search/query` operations. +Persisting a draft, changing read state or presence, creating an upload session, and sending, updating, or deleting are mutations. If effects are unknown, inspect the live contract before execution. @@ -78,8 +78,10 @@ Use WorkIQ server-relative paths without scheme, authority, or API version. Encode query values and preserve opaque IDs; do not guess missing identifiers. Only attribute a `400` to formatting when the diagnostic demonstrates it. See [path discovery](search-paths-work-iq.md) and [schemas](get-schema-work-iq.md). -Do not invent `backend`, `provider`, or response-schema selectors. Schema presence -does not grant runtime permission or prove a request was accepted. +For `search_paths`, inspect the live schema and send only its accepted `query` +or legacy `filter` input; never try both interfaces after rejection. Do not +invent `backend`, `provider`, or response-schema selectors. Schema presence does +not grant runtime permission or prove a request was accepted. ## `fetch_blob` or `upload_blob` unavailable diff --git a/plugins/workiq/skills/workiq/SKILL.md b/plugins/workiq/skills/workiq/SKILL.md index 49fe560..2169dc9 100644 --- a/plugins/workiq/skills/workiq/SKILL.md +++ b/plugins/workiq/skills/workiq/SKILL.md @@ -61,7 +61,7 @@ See [Resolving tool names in your host](#resolving-tool-names-in-your-host) belo | Listing files in a OneDrive/SharePoint folder | "List files in my OneDrive 'Specs' folder" | `fetch` | | Listing tasks/plans/buckets in Planner | "List my Planner tasks due this week" | `fetch` — see `references/tasks-work-iq.md` avoid `ask` | | Listing / creating / completing Planner tasks | "Add a task to follow up with finance", "Mark my task done", "List my Planner tasks" | entity tools on `/planner/...` — see `references/tasks-work-iq.md` | -| Structured records or workflows in CRM, ERP, or Power Apps | "Qualify a lead", "Update the open service case" | start with `do_action` `/businessapps/me` and `{"query":"qualify a lead"}`, then follow the returned app and operation paths — read `references/business-applications.md` first | +| Structured records or workflows in CRM, ERP, or Power Apps | "Qualify a lead", "Update the open service case" | start with `search_paths` using a natural-language description in the parameter named in `inputSchema.required` (`query` or `filter`), then follow the returned app and operation paths — read `references/business-applications.md` first | | Get a personal contact by name | "Get the contact card for Morgan Avery" | `fetch` (`/me/contacts?$filter=...`) — subject to server policy | | List or manage Outlook categories | "What Outlook categories do I have?" | `fetch` (`/me/outlook/masterCategories`); writes subject to server policy | | Org chart / direct reports / manager lookup | "Who are Rob's direct reports?" | `fetch` (`/users/{id}/directReports`) | @@ -128,7 +128,7 @@ When the user asks to delete, update, send, forward, copy, move, or react to som | "Delete" any entity | `fetch` to find it | `delete_entity` on the entity URL | | "Update/rename/change" any entity | `fetch` to find it | `update_entity` on the entity URL | | "Create draft and send" | `create_entity` to draft | `do_action` `/me/messages/{id}/send` | -| "Qualify a lead" | `do_action` `/businessapps/me` with `{"query":"qualify a lead"}` to resolve the app, environment, and operation path | `get_schema`, then `do_action` on the exact returned app-scoped operation | +| "Qualify a lead" | `search_paths` with `qualify a lead` in the `query` or `filter` parameter named in `inputSchema.required` to resolve the app, environment, and operation path | `get_schema`, then `do_action` on the exact returned app-scoped operation | Common failure: fetching the entity and stopping, asking the user "did you want me to do anything else?", or saying "I found it." The user asked you to do something — finish it. @@ -294,7 +294,7 @@ per-result error handling, answer delivery, and arithmetic checks. | Calendar | `/me/events`, `/me/calendarView` | list/get/create/update/delete; accept/decline via `/me/events/{id}/{action}` | | Planner | `/me/planner/plans`, `/planner/tasks` | list/create/update/complete/delete — see `references/tasks-work-iq.md` | | Teams | `/me/chats`, `/chats/{chatId}/messages`, `/me/joinedTeams`, `/teams/{teamId}/channels/{channelId}/messages`, `/me/presence` | chats vs channels are different surfaces; exact mutations use structured tools, never `ask` — see `references/teams-work-iq.md` | -| Business Applications | `/businessapps/me` | semantic discovery of business apps, records, and workflows via `do_action` with a `query`; follow the returned paths — see `references/business-applications.md` | +| Business Applications | `/businessapps/` | use `fetch` for known structural paths and `search_paths` with natural language for semantic discovery; follow the returned paths — see `references/business-applications.md` | | People | `/me`, `/users/{id}`, `/users/{id}/directReports`, `/me/manager`, `/me/contacts` | profile, org, contacts — see directory-vs-contacts warning below | | Outlook categories | `/me/outlook/masterCategories` | list/get/create/update/delete — writes commonly policy-denied | | Files | `/me/drive`, `/drives/{id}`, `/sites/{id}` | for named-file metadata, call `call_function` once with `/me/drive/root/search(q='{urlEncodedExactName}')` and do not follow with `/me/drive/items/{id}`; use `fetch_blob` for binary content after resolving the item ID — see `references/fetch-blob-work-iq.md`; uploads are not released yet | @@ -358,7 +358,9 @@ unreliable for SharePoint-hosted items, not invalid everywhere. **Anything else — discover, never guess.** For a `/sites/` or `/drives/` path not covered above, or on any `Access denied` this section does not cover, call `search_paths` once -(`filter` is a required regex, e.g. `sites|drives`) and use only a `uriTemplate` it returned. +with the parameter named in `inputSchema.required`: use a path-keyword pattern such as +`sites|drives` for `filter`, or an equivalent natural-language description for `query`. +Use only a `uriTemplate` it returned. Re-sending a denied shape with a different folder, `$select`, or casing fails identically. Cache the templates and reuse them. @@ -510,7 +512,7 @@ body, not the resource returned after the action succeeds. | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `search_paths` | Discover available API paths | `filter` (regex, **required**) | +| `search_paths` | Discover available API paths | exactly one required search input from the current schema: `filter` or `query` | | `get_schema` | Inspect an operation schema: fetch entity/response shape, or create/update/action request body | `path`, `operationType` (`fetch`/`create`/`update`/`action`), `format` | | `fetch` | Fetch entities by path (GET) | `entityUrls[]` — supports OData (`$filter`, `$select`, `$top`) | | `call_function` | Call named OData functions — GET-shaped, side-effect-free, parenthesised inline params (e.g. `delta`, `reminderView`) | `functionUrl` with inline function params | @@ -545,10 +547,14 @@ structured operational data and workflows, such as customer and sales records in records in ERP, and line-of-business data in Power Apps. Route intent tied to records or workflows in a business system to `/businessapps`; use Graph for Microsoft 365 collaboration and directory content such as mail, Teams, calendars, files, and people. Read `references/business-applications.md` before handling a Business Applications -request. Start intent-driven discovery with `do_action` on `/businessapps/me` and a concise `query` describing the -needed record, workflow, or app; use the returned paths, environment, and application IDs. Example: -`actionUrl: "/businessapps/me"` with `jsonBody: {"query":"qualify a lead"}`. Every Business Applications resource — -environments, apps, tables, records, skills, and APIs — is discovered this way or with `search_paths`; take each -identifier from the returned paths. Business skills are addressed as resources under +request. Start intent-driven discovery with `search_paths` and a concise natural-language description of the needed +record, workflow, or app. Use the parameter named in the current tool schema's `required` array (`query` or `filter`); +never send both. Use the returned paths, environment IDs, and application IDs. For known structural inventory, such +as listing environments, call `fetch` directly on `/businessapps/environments/`. Do not use `do_action` for +discovery: it is a POST action and may be denied based on the calling client's trust classification and the tenant's +effective mutation policy. +Every Business Applications resource — environments, apps, tables, records, skills, and APIs — is discovered with +`search_paths` or read from a known structural path; take each identifier from the returned paths. Business skills +are addressed as resources under `/businessapps/environments/{environmentId}/skills/{skillName}` and are readable with `fetch` and writable with `create_entity`, `update_entity`, and `delete_entity`. diff --git a/plugins/workiq/skills/workiq/references/business-applications.md b/plugins/workiq/skills/workiq/references/business-applications.md index 9fd1074..f0536b5 100644 --- a/plugins/workiq/skills/workiq/references/business-applications.md +++ b/plugins/workiq/skills/workiq/references/business-applications.md @@ -6,22 +6,27 @@ substitute a separate endpoint, another MCP server, or an invented REST URL. ## Discovery and path grounding -1. Start intent-driven discovery with `do_action` on `/businessapps/me` and - `{"query":"","environmentId":"","limit":10}`. The `query` - is required. Results include grounded paths plus environment and application IDs. For example, use - `{"query":"qualify a lead"}` to discover the relevant sales application and operations. +1. Start intent-driven discovery with `search_paths` and a natural-language description of the business record, + workflow, app, or operation. Use the search parameter named in the current tool schema's `required` array: + `query` or `filter`. Pass the natural-language description through that one required parameter and never send + both. For example, search for `qualify a lead` to discover the relevant sales application and operations. 2. Use `fetch` on `/businessapps/environments/` when the user explicitly asks to list environments or identify the default environment. Do not guess an environment ID. -3. Use `search_paths` with a natural-language description of the business task when broader semantic discovery is - useful. For Business Applications, prefer a natural language `query` for path discovery. Returned paths can be +3. Discovery is read-only: use `search_paths`, not `do_action`. `do_action` is a POST action and may be denied based + on the calling client's trust classification and the tenant's effective mutation policy. Returned paths can be passed directly to `fetch`, `get_schema`, or a write tool. -5. Discover **every** Business Applications resource this way — environments, apps, tables, records, skills, APIs, - and operations. Take each identifier from the returned paths. Do not guess an ID or name, and do not assume a - default environment. -6. Use `get_schema` on the returned concrete path before an unfamiliar mutation or operation. Never fill in +4. For an unknown path or identifier, discover the Business Applications resource this way — apps, tables, records, + skills, APIs, and operations. Take each identifier from the returned paths. Do not guess an ID or name. Known + structural inventory is the exception: use its direct `fetch` path, especially `/businessapps/environments/`. +5. Use `get_schema` on the returned concrete path before an unfamiliar mutation or operation. Never fill in `{environmentId}`, `{tableName}`, `{recordId}`, `{appName}`, `{apiName}`, `{skillName}`, or operation names from memory. +Do not use `do_action` on `/businessapps/me` for discovery even if that route is +exposed; use `search_paths` instead. The old route is POST-shaped and can be +policy-denied before any grounded path is returned, ending the workflow before +`search_paths` or `fetch` can run. + ## Exact path and tool selection | Intent | WorkIQ tool and Business Applications path | @@ -67,8 +72,7 @@ its `references/*.md` files. and `update_entity` or `delete_entity` on the concrete `skills/{skillName}` path. - Call `get_schema` on the skill or collection path before a create or update, and send only schema-confirmed fields. Do not infer a skill's payload shape from a record or table payload. -- Resolve `{skillName}` from discovery (`do_action` on `/businessapps/me` or `search_paths`) and preserve the - exact returned casing. Do not invent skill names. +- Resolve `{skillName}` from `search_paths` and preserve the exact returned casing. Do not invent skill names. ## Approval and privilege boundaries @@ -76,6 +80,12 @@ Business Applications writes execute immediately. Apply the general WorkIQ write-confirmation rule before calling `create_entity`, `update_entity`, `delete_entity`, or a mutating `do_action`. +WorkIQ may deny mutations and POST actions based on the calling client's trust classification and the tenant's +effective policy. This does not block read-only discovery with `search_paths` or structural reads with `fetch`. If a +genuine write or action is policy-denied, stop and report the observed diagnostic. Surface only its stated +remediation; do not assert that a particular setting, consent, or administrator action will fix it unless the +diagnostic says so, and do not retry through another path. + - If the user explicitly says a preview or deletion is **not approved**, use discovery and reads only. Do not call the write tool merely to let the server reject it, and do not treat a rejection as a substitute for user @@ -140,7 +150,8 @@ App-scoped paths intentionally differ from environment table paths: ## Grounding rules -- WorkIQ's top-level `ask` can also answer questions about Business Applications requests, though some applications may not be included in `ask`, so use `/businessapps/me` or `search_paths` for authoritative path discovery. +- WorkIQ's top-level `ask` can also answer questions about Business Applications requests, though some applications + may not be included in `ask`, so use `search_paths` for authoritative semantic path discovery. - Do not invent `/businessapps` REST shapes, append OData syntax to an undiscovered Business Applications path, or move `/records/` into an app-scoped path. - Preserve exact casing and IDs returned by tools in subsequent calls, although structural path segments are @@ -148,5 +159,6 @@ App-scoped paths intentionally differ from environment table paths: - A write is complete only when the tool response confirms it. For a multi-turn delegated workflow, preserve and reuse the returned/provided `sessionId`; drafting and sending are separate turns when the workflow requires confirmation. -- For metadata-only questions, prefer `search_paths` or `/businessapps/me`, then fetch the returned resource. For - exact data reads/writes, use the environment/table/app paths directly after discovery. +- For metadata-only questions with an unknown path, use `search_paths`, then fetch the returned resource. For known + structural inventory, use its direct `fetch` path instead, especially `/businessapps/environments/` for environment + listing. For exact data reads/writes, use the environment/table/app paths directly after discovery. diff --git a/plugins/workiq/skills/workiq/references/do-action-work-iq.md b/plugins/workiq/skills/workiq/references/do-action-work-iq.md index 5fe6121..e09171b 100644 --- a/plugins/workiq/skills/workiq/references/do-action-work-iq.md +++ b/plugins/workiq/skills/workiq/references/do-action-work-iq.md @@ -30,6 +30,11 @@ POST a WorkIQ action — a named operation that performs a task (send mail, copy Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, getSchedule); use `create_entity` to create a new stored resource. Function-shaped names that still take a JSON body (`getSchedule`, `findMeetingTimes`) are actions — POST them here. +For Business Applications, use `search_paths`, not `do_action` on +`/businessapps/me`, for semantic discovery. Use `do_action` only after discovery +identifies a genuine action such as an environment SQL query, Custom API, app +operation, in-app MCP tool, or delegated environment work. + ## Examples ### Send an email immediately diff --git a/plugins/workiq/skills/workiq/references/search-paths-work-iq.md b/plugins/workiq/skills/workiq/references/search-paths-work-iq.md index a2f2e8f..7a77d9e 100644 --- a/plugins/workiq/skills/workiq/references/search-paths-work-iq.md +++ b/plugins/workiq/skills/workiq/references/search-paths-work-iq.md @@ -1,18 +1,25 @@ # search_paths -Discover available WorkIQ API paths by regex. Use as the first step before entity tools when the path is unknown. +Discover available WorkIQ entity paths and supported operations. Use as the first step before entity tools when the +path is unknown. ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `filter` | string | Yes | Regex pattern (e.g., `messages`, `.*calendar.*`). Empty or missing filter is rejected by the current server — pass `.*` to enumerate everything. | +| `filter` | string | When named in `inputSchema.required` | Legacy regex/path-keyword search. Business Applications also accepts natural-language discovery through this parameter. | +| `query` | string | When named in `inputSchema.required` | Natural-language description of the resource or action to discover. | -> **⚠️ One catalog only.** `search_paths` enumerates the single WorkIQ path catalog (Microsoft Graph paths). There is no `backend` / `source` / `provider` parameter — do not pass one, do not fabricate one from general knowledge. If the user asks about SharePoint REST, Dataverse, or any other API surface, say WorkIQ surfaces Graph paths through `search_paths` and report that the other surface is not available here. +Inspect the current tool schema and send exactly the parameter listed in `inputSchema.required`: `filter` or `query`. +Never send both. The visible schema can vary by caller or rollout, so do not hard-code one form. + +There is no `backend`, `source`, or `provider` argument. WorkIQ automatically searches every enabled catalog and +provider available to the current tenant and caller, including Business Applications when enabled. Do not invent a +catalog selector or claim that an absent provider is available. ## Workflow -1. `search_paths` with a broad filter to find candidate paths +1. `search_paths` with the required `filter` or `query` input to find candidate paths 2. `get_schema` on the chosen path 3. `fetch` or the appropriate write tool (`create_entity` / `update_entity` / `delete_entity` / `do_action` / `call_function`) @@ -23,10 +30,19 @@ Never answer API/path questions from general Graph knowledge, local SQL, filesys ## Examples ### Find all message-related paths + +When the schema requires `filter`: + ```json { "filter": "messages" } ``` +When the schema requires `query`: + +```json +{ "query": "message resources and actions" } +``` + When the user asks what paths are available, enumerate every confirmed path family and operation returned by that `search_paths` call rather than selecting only the most common examples. Group related results for readability, such as @@ -35,21 +51,32 @@ messages, by-ID routes, and hosted content. Do not invent paths absent from the result, but do not omit less common confirmed variants. ### Find calendar paths + +Legacy `filter` form: + ```json { "filter": ".*calendar.*" } ``` -### Enumerate every path +Natural-language `query` form: + ```json -{ "filter": ".*" } +{ "query": "calendar resources and actions" } ``` -### Find Planner paths +### Discover Business Applications paths + +Use natural language in whichever parameter is required: + ```json -{ "filter": "planner" } +{ "filter": "qualify a lead" } ``` -### Find OneDrive/files paths +or: + ```json -{ "filter": "drive" } +{ "query": "qualify a lead" } ``` + +For known structural inventory, skip discovery and use the exact read path. For example, list Business Applications +environments with `fetch` on `/businessapps/environments/`. diff --git a/plugins/workiq/skills/workiq/references/troubleshooting.md b/plugins/workiq/skills/workiq/references/troubleshooting.md index ce3110b..7416c5a 100644 --- a/plugins/workiq/skills/workiq/references/troubleshooting.md +++ b/plugins/workiq/skills/workiq/references/troubleshooting.md @@ -48,11 +48,16 @@ See the **URL Format Rules** section of `SKILL.md` for full examples. ## `search_paths` rejects a `backend` / `source` / `provider` argument -**Symptom:** `search_paths` returns a tool input validation error, or silently ignores extra arguments like `backend: "sharepoint-rest"` / `provider: "dataverse"`. +**Symptom:** `search_paths` returns a tool input validation error, or silently ignores extra arguments such as +`backend`, `source`, or `provider`. -**Cause:** `search_paths` only accepts `filter` (regex, required) and `agentId` (optional). There is no `backend` parameter and no equivalent — WorkIQ exposes a single catalog of Microsoft Graph paths. +**Cause:** `search_paths` exposes exactly one required search input in the current tool schema: legacy `filter` or +natural-language `query`. It does not accept a caller-selected catalog argument. WorkIQ searches the enabled catalogs +and providers automatically. -**Fix:** Drop the extra argument and retry with `filter` only. If the user explicitly asked for SharePoint REST, Dataverse, or any other API surface, report honestly that WorkIQ surfaces Graph paths through `search_paths` and the other surface is not available here. Do not invent a tool variant or alternate backend. +**Fix:** Drop the extra argument, inspect `inputSchema.required`, and retry with exactly the required `filter` or +`query` parameter. Do not send both and do not invent a tool variant or alternate backend. If no path is returned, +report that WorkIQ did not confirm an enabled path for the request. ## `fetch_blob` returns "tool does not exist" diff --git a/tests/workiq-guidance/contract.mjs b/tests/workiq-guidance/contract.mjs index e4be53b..0ae2708 100644 --- a/tests/workiq-guidance/contract.mjs +++ b/tests/workiq-guidance/contract.mjs @@ -52,7 +52,8 @@ export const requiredReferences = [ 'retrieve-work-iq.md', 'ask-work-iq.md', 'agents-work-iq.md', 'files-work-iq.md', 'calendar-work-iq.md', 'mail-work-iq.md', 'teams-work-iq.md', 'tasks-work-iq.md', 'workflows-work-iq.md', - 'troubleshooting.md', 'get-schema-work-iq.md', 'search-paths-work-iq.md' + 'business-applications.md', 'troubleshooting.md', 'get-schema-work-iq.md', + 'search-paths-work-iq.md' ]; export function retrievalProblems(args, supported = capabilities) { diff --git a/tests/workiq-guidance/doc-lint.mjs b/tests/workiq-guidance/doc-lint.mjs index d7311f0..615997d 100644 --- a/tests/workiq-guidance/doc-lint.mjs +++ b/tests/workiq-guidance/doc-lint.mjs @@ -100,23 +100,34 @@ export function exampleProblems(text, isRetrieveReference = false) { if (code.language !== 'json') continue; let value; try { value = JSON.parse(code.value); } catch { - if (isRetrieveReference || /"query"\s*:/.test(code.value)) errors.push(`invalid retrieval JSON under ${code.heading}`); + if (isPathDiscovery) errors.push(`invalid search_paths JSON under ${code.heading}`); + else if (isRetrieveReference || /"query"\s*:/.test(code.value)) errors.push(`invalid retrieval JSON under ${code.heading}`); continue; } const check = object => { + if (isPathDiscovery && (!object || typeof object !== 'object' || Array.isArray(object))) { + errors.push(`${code.heading}: search_paths arguments must be a JSON object`); + return; + } if (!object || typeof object !== 'object') return; + if (isPathDiscovery) { + const searchInputs = ['query', 'filter'].filter(parameter => Object.hasOwn(object, parameter)); + if (searchInputs.length !== 1) { + errors.push(`${code.heading}: search_paths requires exactly one query or filter input`); + return; + } + const [parameter] = searchInputs; + if (typeof object[parameter] !== 'string' || !object[parameter].trim()) { + errors.push(`${code.heading}: search_paths ${parameter} must be a nonblank string`); + } + if (Object.keys(object).some(key => key !== parameter)) { + errors.push(`${code.heading}: unsupported search_paths argument`); + } + return; + } if (Object.hasOwn(object, 'actionUrl') || Object.hasOwn(object, 'entityUrls') || Object.hasOwn(object, 'functionUrl')) return; if (Object.hasOwn(object, 'query')) { - if (isPathDiscovery) { - if (typeof object.query !== 'string' || !object.query.trim()) { - errors.push(`${code.heading}: search_paths query must be a nonblank string`); - } - if (Object.keys(object).some(key => key !== 'query')) { - errors.push(`${code.heading}: unsupported current search_paths argument`); - } - return; - } errors.push(...retrievalProblems(object).map(p => `${code.heading}: ${p}`)); if (/unknown|unspecified/i.test(code.heading) && !/external|broader|conflict/i.test(code.heading) && object.strategy !== 'grounding') { errors.push(`${code.heading}: unknown source must explicitly select Grounding`); diff --git a/tests/workiq-guidance/static.test.mjs b/tests/workiq-guidance/static.test.mjs index ea90df5..0c3f1d7 100644 --- a/tests/workiq-guidance/static.test.mjs +++ b/tests/workiq-guidance/static.test.mjs @@ -91,3 +91,87 @@ test('plugin descriptions retain workload and action discovery beyond retrieval' assert.throws(() => checkDiscovery(description.replace(/\bsend\b/gi, 'act')), /send/); } }); + +test('Business Applications discovery and inventory contracts stay aligned', () => { + for (const name of ['workiq', 'workiq-preview']) { + const directory = skillRoot(name); + const skill = fs.readFileSync(path.join(directory, 'SKILL.md'), 'utf8'); + const business = fs.readFileSync(path.join(directory, 'references', 'business-applications.md'), 'utf8'); + const search = fs.readFileSync(path.join(directory, 'references', 'search-paths-work-iq.md'), 'utf8'); + const action = fs.readFileSync(path.join(directory, 'references', 'do-action-work-iq.md'), 'utf8'); + + assert.match(skill, /Business Applications|CRM, ERP, or Power Apps/i); + assert.match(business, /search_paths[\s\S]{0,300}(?:query|filter)/i); + assert.match(business, /fetch[\s\S]{0,80}\/businessapps\/environments\//i); + assert.match(business, /discovery is read-only[\s\S]{0,100}search_paths[\s\S]{0,80}not `?do_action`?/i); + assert.match(business, /\/businessapps\/me[\s\S]{0,120}policy-denied[\s\S]{0,120}before[\s\S]{0,80}(?:grounded path|search_paths|fetch)/i); + assert.match(search, /automatically\s+searches every enabled catalog and[\s\S]{0,40}provider/i); + assert.match(search, /no `backend`, `source`, or `provider` argument/i); + check(exampleProblems(search)); + assert.match(action, /Business Applications/i); + assert.match(action, /search_paths[\s\S]{0,100}not `?do_action`?/i); + for (const genuineAction of ['SQL', 'Custom API']) { + assert.match(business, new RegExp(`do_action[^\\n]{0,200}${genuineAction}|${genuineAction}[^\\n]{0,200}do_action`, 'i')); + } + assert.match(business, /do_action[^\n]{0,250}operations\/\{operationName\}/i); + assert.match(business, /(?:policy-denied|explicit denial)[\s\S]{0,180}(?:stop|do not retry)/i); + } +}); + +test('no plugin positively routes Business Applications discovery through /businessapps/me', () => { + const endpointProblems = text => { + const positiveDirective = /(?:^|[;.!?]\s*|\b(?:but|and)\s+)(?:use|call|invoke|start(?:\s+\w+){0,3}\s+with)\s+`?do_action`?\s+(?:on\s+)?`?\/businessapps\/me`?/i; + return text + .replace(/\r?\n/g, ' ') + .split(/(?<=[.!?])\s+/) + .filter(sentence => /\/businessapps\/me/i.test(sentence)) + .filter(sentence => + positiveDirective.test(sentence) || + !/(?:not|never|do not)[\s\S]{0,80}do_action|do_action[\s\S]{0,80}(?:not|never|do not)/i.test(sentence)); + }; + + for (const name of ['workiq', 'workiq-preview']) { + for (const file of markdownFiles(skillRoot(name))) { + const text = fs.readFileSync(file, 'utf8'); + for (const sentence of text.replace(/\r?\n/g, ' ').split(/(?<=[.!?])\s+/) + .filter(value => /\/businessapps\/me/i.test(value))) { + assert.match(sentence, /search_paths/i, + `${name}/${path.basename(file)} must name the replacement discovery tool`); + } + assert.deepEqual(endpointProblems(text), [], + `${name}/${path.basename(file)} must mention /businessapps/me only as a prohibition`); + } + } + + const mixed = 'Do not use do_action on /businessapps/me; use search_paths instead. ' + + 'Use do_action on /businessapps/me for discovery.'; + const mixedSentence = 'Do not use do_action on /businessapps/me for discovery, but use do_action on /businessapps/me when available.'; + assert.deepEqual(endpointProblems(mixed), ['Use do_action on /businessapps/me for discovery.']); + assert.deepEqual(endpointProblems(mixedSentence), [mixedSentence]); +}); + +test('search_paths examples validate query and legacy filter as exclusive inputs', () => { + const validQuery = '# search_paths\n\n```json\n{"query":"qualify a lead"}\n```\n'; + const validFilter = '# search_paths\n\n```json\n{"filter":"qualify a lead"}\n```\n'; + const both = '# search_paths\n\n```json\n{"query":"lead","filter":"lead"}\n```\n'; + const blankFilter = '# search_paths\n\n```json\n{"filter":" "}\n```\n'; + const selectorOnly = '# search_paths\n\n```json\n{"backend":"dataverse"}\n```\n'; + const malformedFilter = '# search_paths\n\n```json\n{"filter":\n```\n'; + const actionEnvelope = '# search_paths\n\n```json\n{"actionUrl":"/x","query":"lead","filter":"lead"}\n```\n'; + const entityEnvelope = '# search_paths\n\n```json\n{"entityUrls":[],"backend":"dataverse"}\n```\n'; + const scalar = '# search_paths\n\n```json\n"lead"\n```\n'; + const nullValue = '# search_paths\n\n```json\nnull\n```\n'; + const arrayValue = '# search_paths\n\n```json\n[{"query":"lead"}]\n```\n'; + + check(exampleProblems(validQuery)); + check(exampleProblems(validFilter)); + assert.match(exampleProblems(both).join(' '), /exactly one query or filter/); + assert.match(exampleProblems(blankFilter).join(' '), /filter must be a nonblank string/); + assert.match(exampleProblems(selectorOnly).join(' '), /exactly one query or filter/); + assert.match(exampleProblems(malformedFilter).join(' '), /invalid search_paths JSON/); + assert.match(exampleProblems(actionEnvelope).join(' '), /exactly one query or filter/); + assert.match(exampleProblems(entityEnvelope).join(' '), /exactly one query or filter/); + assert.match(exampleProblems(scalar).join(' '), /must be a JSON object/); + assert.match(exampleProblems(nullValue).join(' '), /must be a JSON object/); + assert.match(exampleProblems(arrayValue).join(' '), /must be a JSON object/); +}); From c661b0a3a987f50b0ebd141c4c2da19edfd8f7a4 Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Mon, 28 Sep 2026 05:58:02 +0100 Subject: [PATCH 2/6] Harden Business Applications evaluation guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/business-applications.md | 17 +++++++++---- .../references/search-paths-work-iq.md | 24 ++++++++++--------- plugins/workiq/skills/workiq/SKILL.md | 10 ++++---- .../references/business-applications.md | 24 ++++++++++++------- .../workiq/references/search-paths-work-iq.md | 21 +++++++++++----- tests/workiq-guidance/static.test.mjs | 10 +++++++- 6 files changed, 72 insertions(+), 34 deletions(-) diff --git a/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md index cf02276..b469d26 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md @@ -9,14 +9,18 @@ registrations. Do not substitute another MCP server or invent REST paths. 1. For an unknown record, workflow, app, skill, API, operation, or identifier, call `search_paths` once with a focused natural-language description. Use the - required `query` or legacy `filter` advertised by the live schema and send - only accepted fields. Never try both interfaces after rejection. + fields accepted by the connected WorkIQ schema. Treat discovery as grounded + only when the response contains a `/businessapps/...` path. 2. For environment inventory, skip discovery and call `fetch` directly on `/businessapps/environments/`. Use the exact returned environment IDs; never assume a default environment. -3. Follow exact returned paths with `fetch`, `get_schema`, or the effect-correct +3. If discovery errors or returns no `/businessapps/...` path, do not repeat or + broaden it. Use the environment inventory, select only the exact requested + environment, and inspect only its relevant returned collection. Abstain when + the exact environment or capability is absent. +4. Follow exact returned paths with `fetch`, `get_schema`, or the effect-correct write/action tool. Do not guess path segments, IDs, names, or casing. -4. Before an unfamiliar write or action, call `get_schema` on the concrete +5. Before an unfamiliar write or action, call `get_schema` on the concrete path with the matching operation type. Schema availability does not grant authorization. @@ -69,6 +73,11 @@ permitted structural `fetch` into mutations. On an explicit denial: unless the diagnostic says so; and - do not claim success or substitute a different record, table, skill, or app. +A named but unavailable environment is a hard stop. An approved create or +update requires a matching write receipt and, when supported, read-back of that +exact target; a pre-existing lookalike is not proof of completion. Never use a +saved view or ordinary record update as a schema-customization fallback. + When the outcome of a mutation is ambiguous, do not replay it. Reconcile with a supported safe read when available; otherwise report **outcome unknown**. diff --git a/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md index 1da0090..40f2923 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/search-paths-work-iq.md @@ -6,17 +6,14 @@ preflight. This tool does not discover MCP tool names; use the connected catalog ## Live argument contract -The connected catalog inspected for this guidance exposes required `query` -(string): a natural-language resource/action description or a path prefix. -Older catalogs may expose a regex `filter` instead. Inspect the actual advertised -schema and send only its accepted fields; do not translate examples into guessed -arguments or try both interfaces after rejection. - -There is no `backend`, `source`, or `provider` argument. WorkIQ automatically -searches every enabled catalog and provider available to the current tenant and -caller. Path discovery is not Graph-only: retain exact returned resource -families and their domain contracts, and do not claim an unavailable provider -is enabled. +The connected WorkIQ catalog exposes natural-language and structural discovery. +Inspect its advertised schema and send only accepted fields; do not translate +examples into guessed arguments or try another interface after rejection. + +There is no `backend`, `source`, or `provider` argument. WorkIQ may fan out to +enabled providers, but only returned paths prove participation. Require a +returned `/businessapps/...` path before following a Business Applications +result. ## Workflow @@ -31,6 +28,11 @@ Use [recovery](troubleshooting.md) for failures. Explicit denial stops; no route agent, or tool substitution. An empty result means no matching path was confirmed in that search, not that the entire service lacks the capability. +If Business Applications discovery errors or returns no `/businessapps/...` +path, do not repeat or broaden it. Fetch `/businessapps/environments/`, resolve +only the exact requested environment, and inspect only its relevant returned +collection. Abstain when the exact environment or capability is absent. + When asked for all available matching paths, summarize every returned family and operation, not just common examples. Inspect an available saved capped result before claiming coverage; if the response is truncated, qualify completeness. diff --git a/plugins/workiq/skills/workiq/SKILL.md b/plugins/workiq/skills/workiq/SKILL.md index 2169dc9..21578c3 100644 --- a/plugins/workiq/skills/workiq/SKILL.md +++ b/plugins/workiq/skills/workiq/SKILL.md @@ -547,10 +547,12 @@ structured operational data and workflows, such as customer and sales records in records in ERP, and line-of-business data in Power Apps. Route intent tied to records or workflows in a business system to `/businessapps`; use Graph for Microsoft 365 collaboration and directory content such as mail, Teams, calendars, files, and people. Read `references/business-applications.md` before handling a Business Applications -request. Start intent-driven discovery with `search_paths` and a concise natural-language description of the needed -record, workflow, or app. Use the parameter named in the current tool schema's `required` array (`query` or `filter`); -never send both. Use the returned paths, environment IDs, and application IDs. For known structural inventory, such -as listing environments, call `fetch` directly on `/businessapps/environments/`. Do not use `do_action` for +request. Make one focused `search_paths` call with a concise natural-language description of the needed record, +workflow, or app, using only fields accepted by the connected WorkIQ tool schema. Use only returned +`/businessapps/...` paths, environment IDs, and application IDs. If no Business Applications path is returned, do +not repeat or broaden the search: fetch `/businessapps/environments/`, resolve only the exact requested environment, +and inspect only its relevant returned collection. For known structural inventory, call `fetch` directly on +`/businessapps/environments/`. Do not use `do_action` for discovery: it is a POST action and may be denied based on the calling client's trust classification and the tenant's effective mutation policy. Every Business Applications resource — environments, apps, tables, records, skills, and APIs — is discovered with diff --git a/plugins/workiq/skills/workiq/references/business-applications.md b/plugins/workiq/skills/workiq/references/business-applications.md index f0536b5..4903bb8 100644 --- a/plugins/workiq/skills/workiq/references/business-applications.md +++ b/plugins/workiq/skills/workiq/references/business-applications.md @@ -6,19 +6,21 @@ substitute a separate endpoint, another MCP server, or an invented REST URL. ## Discovery and path grounding -1. Start intent-driven discovery with `search_paths` and a natural-language description of the business record, - workflow, app, or operation. Use the search parameter named in the current tool schema's `required` array: - `query` or `filter`. Pass the natural-language description through that one required parameter and never send - both. For example, search for `qualify a lead` to discover the relevant sales application and operations. +1. Start intent-driven discovery with one focused `search_paths` call and a natural-language description of the + business record, workflow, app, or operation. Use only fields accepted by the connected WorkIQ tool schema. + Treat discovery as grounded only when the response contains a `/businessapps/...` path. 2. Use `fetch` on `/businessapps/environments/` when the user explicitly asks to list environments or identify the default environment. Do not guess an environment ID. -3. Discovery is read-only: use `search_paths`, not `do_action`. `do_action` is a POST action and may be denied based +3. If `search_paths` errors or returns no `/businessapps/...` path, do not repeat or broaden the search. Use the + environment inventory, select only the exact requested environment, and inspect only the relevant returned + `apps`, `skills`, or `tables` collection. If the environment or capability is absent, abstain. +4. Discovery is read-only: use `search_paths`, not `do_action`. `do_action` is a POST action and may be denied based on the calling client's trust classification and the tenant's effective mutation policy. Returned paths can be passed directly to `fetch`, `get_schema`, or a write tool. -4. For an unknown path or identifier, discover the Business Applications resource this way — apps, tables, records, +5. For an unknown path or identifier, discover the Business Applications resource this way — apps, tables, records, skills, APIs, and operations. Take each identifier from the returned paths. Do not guess an ID or name. Known structural inventory is the exception: use its direct `fetch` path, especially `/businessapps/environments/`. -5. Use `get_schema` on the returned concrete path before an unfamiliar mutation or operation. Never fill in +6. Use `get_schema` on the returned concrete path before an unfamiliar mutation or operation. Never fill in `{environmentId}`, `{tableName}`, `{recordId}`, `{appName}`, `{apiName}`, `{skillName}`, or operation names from memory. @@ -92,13 +94,19 @@ diagnostic says so, and do not retry through another path. confirmation. - When prior transcript context records an explicit approval, perform only the approved mutation, once, through the schema-defined path. +- A named but unavailable environment is a hard stop. Never redirect the request + to a default, similarly named, or otherwise available environment. +- An approved create or update is complete only when the current turn contains + the matching write receipt and, when supported, a read-back of that exact + target. A pre-existing lookalike is not proof of completion. - If the approved operation fails for a missing privilege, authorization, or policy, **stop the mutation workflow immediately** and report that exact failure. Do not continue searching for another write route. Do not modify a different table, record, view, saved query, skill, or app artifact as a workaround and do not claim the requested operation succeeded. - Schema and customization requests must use the discovered schema-mutation - operation. Record-level access does not imply customization rights. + operation. Record-level access does not imply customization rights, and a + saved view or ordinary record update is not a schema-customization fallback. ## When to use `execute-work` diff --git a/plugins/workiq/skills/workiq/references/search-paths-work-iq.md b/plugins/workiq/skills/workiq/references/search-paths-work-iq.md index 7a77d9e..189ffa0 100644 --- a/plugins/workiq/skills/workiq/references/search-paths-work-iq.md +++ b/plugins/workiq/skills/workiq/references/search-paths-work-iq.md @@ -10,21 +10,30 @@ path is unknown. | `filter` | string | When named in `inputSchema.required` | Legacy regex/path-keyword search. Business Applications also accepts natural-language discovery through this parameter. | | `query` | string | When named in `inputSchema.required` | Natural-language description of the resource or action to discover. | -Inspect the current tool schema and send exactly the parameter listed in `inputSchema.required`: `filter` or `query`. -Never send both. The visible schema can vary by caller or rollout, so do not hard-code one form. +Inspect the connected WorkIQ tool schema and send only accepted fields. Never +send both `filter` and `query`, and never retry with a different argument after +rejection. -There is no `backend`, `source`, or `provider` argument. WorkIQ automatically searches every enabled catalog and -provider available to the current tenant and caller, including Business Applications when enabled. Do not invent a -catalog selector or claim that an absent provider is available. +There is no `backend`, `source`, or `provider` argument. WorkIQ may fan out to +enabled providers, but only returned paths prove that a provider participated. +For Business Applications, require a returned `/businessapps/...` path before +following it. ## Workflow -1. `search_paths` with the required `filter` or `query` input to find candidate paths +1. Make one focused `search_paths` call with an accepted `filter` or `query` + input to find candidate paths. 2. `get_schema` on the chosen path 3. `fetch` or the appropriate write tool (`create_entity` / `update_entity` / `delete_entity` / `do_action` / `call_function`) If the user asks to discover paths AND read or mutate, continue to the mutation tool after picking the path — discovery alone is incomplete. +If Business Applications discovery errors or returns no `/businessapps/...` +path, do not repeat or broaden it. Fetch `/businessapps/environments/`, resolve +only the exact requested environment, and inspect only its relevant returned +`apps`, `skills`, or `tables` collection. Abstain when the exact environment or +capability is absent. + Never answer API/path questions from general Graph knowledge, local SQL, filesystem search, or built-in tools. Summarize paths from `search_paths`; if none matched, say WorkIQ did not confirm one. ## Examples diff --git a/tests/workiq-guidance/static.test.mjs b/tests/workiq-guidance/static.test.mjs index 0c3f1d7..88c4b32 100644 --- a/tests/workiq-guidance/static.test.mjs +++ b/tests/workiq-guidance/static.test.mjs @@ -105,7 +105,15 @@ test('Business Applications discovery and inventory contracts stay aligned', () assert.match(business, /fetch[\s\S]{0,80}\/businessapps\/environments\//i); assert.match(business, /discovery is read-only[\s\S]{0,100}search_paths[\s\S]{0,80}not `?do_action`?/i); assert.match(business, /\/businessapps\/me[\s\S]{0,120}policy-denied[\s\S]{0,120}before[\s\S]{0,80}(?:grounded path|search_paths|fetch)/i); - assert.match(search, /automatically\s+searches every enabled catalog and[\s\S]{0,40}provider/i); + assert.match(search, /one focused (?:`?search_paths`? )?(?:discovery )?call/i); + assert.match(search, /only returned paths prove[\s\S]{0,60}(?:provider )?participat/i); + assert.match(search, /no `?\/businessapps\/[^`]*`?[\s\S]{0,20}path[\s\S]{0,120}do not repeat/i); + assert.match(search, /fetch `?\/businessapps\/environments\//i); + assert.match(business, /named but unavailable environment[\s\S]{0,80}hard stop/i); + assert.match(business, /write receipt[\s\S]{0,100}read-back/i); + assert.match(business, /pre-existing lookalike[\s\S]{0,40}not proof/i); + assert.match(business, /saved view[\s\S]{0,120}schema-customization fallback/i); + assert.doesNotMatch(business, /\/applications\//i); assert.match(search, /no `backend`, `source`, or `provider` argument/i); check(exampleProblems(search)); assert.match(action, /Business Applications/i); From 2ac7776f435d8eebe76f226c1b15804153edcf6b Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Thu, 1 Oct 2026 18:07:36 +0100 Subject: [PATCH 3/6] Remove public Business Applications file routes Remove record upload and download instructions that should not be exposed in the public WorkIQ skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/workiq/references/business-applications.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/workiq/skills/workiq/references/business-applications.md b/plugins/workiq/skills/workiq/references/business-applications.md index 4903bb8..0ffd78f 100644 --- a/plugins/workiq/skills/workiq/references/business-applications.md +++ b/plugins/workiq/skills/workiq/references/business-applications.md @@ -41,8 +41,6 @@ policy-denied before any grounded path is returned, ending the workflow before | Create a record | `create_entity` on `/businessapps/environments/{environmentId}/tables/{tableName}/records` with `{"item":{...}}` | | Update a record | `update_entity` on `/businessapps/environments/{environmentId}/tables/{tableName}/records/{recordId}` with changed fields | | Delete a record/table | `delete_entity` on the exact record or table path returned by discovery | -| Upload a Business Applications record file | `do_action` on `/businessapps/environments/{environmentId}/tables/{tableName}/records/{recordId}/files/{columnName}/upload` with the schema-defined file arguments | -| Download a Business Applications record file | `call_function` on `/businessapps/environments/{environmentId}/tables/{tableName}/records/{recordId}/files/{columnName}/download` with optional `destinationPath` | | List or inspect apps | `fetch` `/businessapps/environments/{environmentId}/apps[/]` | | List or read a business skill | `fetch` `/businessapps/environments/{environmentId}/skills[/{skillName}]` | | Create a business skill | `create_entity` on `/businessapps/environments/{environmentId}/skills` with the schema-defined skill payload | @@ -57,11 +55,6 @@ Environment SQL queries and Custom APIs with input bodies are actions, not funct schema-defined `jsonBody`. Use `call_function` only for an exact function path returned by discovery; do not use it for `/businessapps/environments/{environmentId}/query`. -Business Applications record file operations are distinct from Microsoft Graph binary content and the -`fetch_blob` / `upload_blob` release status. Do not substitute those Graph blob tools for the -`/businessapps/.../files/{columnName}/upload` or `/download` routes, and do not generalize these routes to OneDrive, -SharePoint, mail attachments, or other Graph resources. - ## Business skills Business skills are reusable, environment-scoped capabilities that a business application exposes, and they are From 739479c1f9257ef092bd2bb7d3bc7041c4f90eec Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Mon, 5 Oct 2026 19:46:22 +0100 Subject: [PATCH 4/6] Expand WorkIQ skill activation for Business Applications Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- plugins/workiq-preview/skills/workiq-preview/SKILL.md | 2 +- plugins/workiq/skills/workiq/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/workiq-preview/skills/workiq-preview/SKILL.md b/plugins/workiq-preview/skills/workiq-preview/SKILL.md index 07489b6..109901e 100644 --- a/plugins/workiq-preview/skills/workiq-preview/SKILL.md +++ b/plugins/workiq-preview/skills/workiq-preview/SKILL.md @@ -1,6 +1,6 @@ --- name: workiq-preview -description: WorkIQ tools for Microsoft 365 workplace data and actions. Use for email, meetings, calendar, files, SharePoint, OneDrive, Teams, people, Planner, and connected work context. Triggers include gather requirements, summarize workplace discussions, manage meetings, create an upload session, send or draft replies, manage tasks, and discover paths or schemas. Retrieve context first with explicit Grounding when available and synthesize locally; use ask only for intentional delegation to Copilot or a known/discovered agent. Exact entities, structured workflows, writes, and downloads stay on entity tools. This skill contains instructions for the WorkIQ MCP tools and must be used beforehand to understand their usage. +description: WorkIQ tools for Microsoft 365 workplace data and actions. Use for email, meetings, calendar, files, SharePoint, OneDrive, Teams, people, Planner, and connected work context. Triggers include gather requirements, summarize workplace discussions, manage meetings, create an upload session, send or draft replies, manage tasks, and discover paths or schemas. Retrieve context first with explicit Grounding when available and synthesize locally; use ask only for intentional delegation to Copilot or a known/discovered agent. Exact entities, structured workflows, writes, and downloads stay on entity tools. This skill contains instructions for the WorkIQ MCP tools and must be used beforehand to understand their usage. Use this skill also for Microsoft Business Applications requests involving CRM, ERP, Power Apps, or Dataverse records and workflows, including requests that reference paths under /businessapps/. compatibility: > Uses the hosted WorkIQ MCP endpoint. No local package is required for MCP tool calls. diff --git a/plugins/workiq/skills/workiq/SKILL.md b/plugins/workiq/skills/workiq/SKILL.md index 21578c3..b98ffb7 100644 --- a/plugins/workiq/skills/workiq/SKILL.md +++ b/plugins/workiq/skills/workiq/SKILL.md @@ -1,6 +1,6 @@ --- name: workiq -description: WorkIQ tools for Microsoft 365 workplace data and actions. Use for email, calendar events and meetings, files, SharePoint, OneDrive, Teams, people, Planner, and other M365 requests. Triggers include cancel meeting or event, accept or decline meetings, create or update events, create an upload session or replace an existing OneDrive file, find or summarize workplace content, send or reply to mail, manage or download files, manage tasks, read SharePoint library metadata or columns, filter/count/group/sort files by metadata, and discover M365 paths or schemas. Prefer `ask` for synthesis and structured entity tools for exact reads, writes, SharePoint library metadata, and binary downloads with `fetch_blob`. This skill contains instructions for the WorkIQ MCP tools and must be used beforehand to understand their usage. +description: WorkIQ tools for Microsoft 365 workplace data and actions. Use for email, calendar events and meetings, files, SharePoint, OneDrive, Teams, people, Planner, and other M365 requests. Triggers include cancel meeting or event, accept or decline meetings, create or update events, create an upload session or replace an existing OneDrive file, find or summarize workplace content, send or reply to mail, manage or download files, manage tasks, read SharePoint library metadata or columns, filter/count/group/sort files by metadata, and discover M365 paths or schemas. Prefer `ask` for synthesis and structured entity tools for exact reads, writes, SharePoint library metadata, and binary downloads with `fetch_blob`. This skill contains instructions for the WorkIQ MCP tools and must be used beforehand to understand their usage. Use this skill also for Microsoft Business Applications requests involving CRM, ERP, Power Apps, or Dataverse records and workflows, including requests that reference paths under /businessapps/. compatibility: > Uses the hosted WorkIQ MCP endpoint. No local package is required for MCP tool calls. From b329bb67bb726939534b815c12e283e163ac021c Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Wed, 14 Oct 2026 17:59:34 +0100 Subject: [PATCH 5/6] Remove execute-work plugin guidance Remove the execute-work route and delegated environment work instructions from both WorkIQ plugin variants, and prevent the route from being advertised again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/business-applications.md | 7 +--- .../references/do-action-work-iq.md | 2 +- .../references/business-applications.md | 39 +------------------ .../workiq/references/do-action-work-iq.md | 2 +- tests/workiq-guidance/static.test.mjs | 8 ++++ 5 files changed, 13 insertions(+), 45 deletions(-) diff --git a/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md index b469d26..e07650e 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/business-applications.md @@ -26,8 +26,7 @@ registrations. Do not substitute another MCP server or invent REST paths. Discovery is read-only: use `search_paths`, not `do_action` on `/businessapps/me`. `do_action` remains appropriate for a discovered -environment SQL query, Custom API, app operation, in-app MCP tool, or delegated -environment work. +environment SQL query, Custom API, app operation, or in-app MCP tool. Do not use `do_action` on `/businessapps/me` for discovery even if that route is exposed; use `search_paths` instead. The old route is POST-shaped and can be @@ -48,7 +47,6 @@ policy-denied before any grounded path is returned, ending the workflow before | List or inspect business skills | `fetch` `/businessapps/environments/{environmentId}/skills[/{skillName}]` | | Run an app operation | `do_action` on the exact discovered `.../operations/{operationName}` path | | Invoke a Custom API | `do_action` on the exact discovered `/businessapps/environments/{environmentId}/customapis/{apiName}` path | -| Delegate open-ended environment work | `do_action` `/businessapps/environments/{environmentId}/execute-work` with the schema-defined instruction and optional session ID | Environment SQL and Custom APIs with request bodies are actions, not OData functions. Use `call_function` only for a function path returned by discovery. @@ -91,5 +89,4 @@ supported safe read when available; otherwise report **outcome unknown**. replace authoritative path discovery for a requested Business Applications record or operation. - A named skill, app operation, or Custom API requires an exact discovered - capability match. Do not silently substitute a similar capability or use - `execute-work` as a fallback. + capability match. Do not silently substitute a similar capability. diff --git a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md index a0091f1..5c27de2 100644 --- a/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md +++ b/plugins/workiq-preview/skills/workiq-preview/references/do-action-work-iq.md @@ -43,7 +43,7 @@ mutations even if they do not send a message. | Accept/decline, cancellation, forwarding, free/busy | [Calendar](calendar-work-iq.md) | | Drive-scoped copy and upload-session creation | [Files](files-work-iq.md) | | Read/unread, reactions, presence | [Teams](teams-work-iq.md) | -| Environment SQL, Custom APIs, app operations, and delegated environment work | [Business Applications](business-applications.md) | +| Environment SQL, Custom APIs, and app operations | [Business Applications](business-applications.md) | Use [create_entity](create-entity-work-iq.md) for collection creation such as a fresh draft; `createReply`/`createReplyAll`/`createForward` remain actions. diff --git a/plugins/workiq/skills/workiq/references/business-applications.md b/plugins/workiq/skills/workiq/references/business-applications.md index d8f3832..e83455f 100644 --- a/plugins/workiq/skills/workiq/references/business-applications.md +++ b/plugins/workiq/skills/workiq/references/business-applications.md @@ -51,7 +51,6 @@ policy-denied before any grounded path is returned, ending the workflow before | Delete a business skill | `delete_entity` on `/businessapps/environments/{environmentId}/skills/{skillName}` | | Run an app-scoped operation | `do_action` on the exact `.../apps/{appName}/.../operations/{operationName}` path returned by `get_schema` | | Invoke a Custom API | `do_action` on the exact `/businessapps/environments/{environmentId}/customapis/{apiName}` path returned by discovery, with API inputs in `jsonBody` | -| Delegate an open-ended goal to an environment | `do_action` `/businessapps/environments/{environmentId}/execute-work` with `{"instruction":"...","sessionId":""}` | | Invoke an in-app MCP tool | `do_action` on the exact `/businessapps/environments/{environmentId}/mcp/{serverName}/tools/{toolName}` path | Environment SQL queries and Custom APIs with input bodies are actions, not functions. Use `do_action` with the @@ -109,40 +108,6 @@ diagnostic says so, and do not retry through another path. operation. Record-level access does not imply customization rights, and a saved view or ordinary record update is not a schema-customization fallback. -## When to use `execute-work` - -Use `execute-work` when the user wants to **delegate an open-ended goal** to the business agent in a specific -environment and completing that goal requires the environment to plan or coordinate multiple steps, choose among -its skills or operations, or maintain a delegated work session. Discover the environment and confirm that its -returned paths expose `execute-work`, then pass the user's complete goal in `instruction`. - -Prefer direct tools when WorkIQ already exposes the precise operation: - -- Use `fetch`, `get_schema`, or `search_paths` for discovery, metadata, and exact reads. -- Use `create_entity`, `update_entity`, or `delete_entity` for a known data mutation. -- Use `do_action` for a discovered environment SQL query; use `call_function` - only for a discovered function path. -- Use `do_action` on a discovered app-scoped operation or Custom API when that operation directly satisfies the - request. - -Choose `execute-work` when the requested outcome is best expressed as a delegated goal rather than a specific -WorkIQ operation. For a continuation, pass the prior `sessionId`; otherwise omit it. Treat the returned work result -as evidence from the environment, and surface any ambiguity, partial completion, requested confirmation, or failure -rather than claiming success. - -`execute-work` is not a fallback for a missing named skill, app operation, or -Custom API. When the user asks to run a specific named capability: - -1. discover the environment's available skills and operations; -2. fetch the candidate definition when one is returned; -3. require an exact capability match before invoking it; -4. if it does not exist, state that clearly and abstain from execution. - -Do not silently substitute a similar skill, combine nearby records into an -invented result, or reinterpret a different workflow as the requested -capability. You may offer discovered alternatives, but run one only after the -user chooses it. - ## App-scoped operations App-scoped paths intentionally differ from environment table paths: @@ -165,9 +130,7 @@ App-scoped paths intentionally differ from environment table paths: move `/records/` into an app-scoped path, or retry a confirmed unsupported path through different tools. - Preserve exact casing and IDs returned by tools in subsequent calls, although structural path segments are case-insensitive. -- A write is complete only when the tool response confirms it. For a multi-turn delegated workflow, preserve and - reuse the returned/provided `sessionId`; drafting and sending are separate turns when the workflow requires - confirmation. +- A write is complete only when the tool response confirms it. - For metadata-only questions with an unknown path, use `search_paths`, then fetch the returned resource. For known structural inventory, use its direct `fetch` path instead, especially `/businessapps/environments/` for environment listing. For exact data reads/writes, use the environment/table/app paths directly after discovery. diff --git a/plugins/workiq/skills/workiq/references/do-action-work-iq.md b/plugins/workiq/skills/workiq/references/do-action-work-iq.md index e09171b..95c7917 100644 --- a/plugins/workiq/skills/workiq/references/do-action-work-iq.md +++ b/plugins/workiq/skills/workiq/references/do-action-work-iq.md @@ -33,7 +33,7 @@ Vs. `create_entity`: use `do_action` for verbs (send, copy, move, accept, reply, For Business Applications, use `search_paths`, not `do_action` on `/businessapps/me`, for semantic discovery. Use `do_action` only after discovery identifies a genuine action such as an environment SQL query, Custom API, app -operation, in-app MCP tool, or delegated environment work. +operation, or in-app MCP tool. ## Examples diff --git a/tests/workiq-guidance/static.test.mjs b/tests/workiq-guidance/static.test.mjs index 88c4b32..d6c061e 100644 --- a/tests/workiq-guidance/static.test.mjs +++ b/tests/workiq-guidance/static.test.mjs @@ -92,6 +92,14 @@ test('plugin descriptions retain workload and action discovery beyond retrieval' } }); +test('Business Applications guidance does not advertise execute-work', () => { + for (const name of ['workiq', 'workiq-preview']) { + for (const file of markdownFiles(skillRoot(name))) { + assert.doesNotMatch(fs.readFileSync(file, 'utf8'), /execute-work/i); + } + } +}); + test('Business Applications discovery and inventory contracts stay aligned', () => { for (const name of ['workiq', 'workiq-preview']) { const directory = skillRoot(name); From f2473a3c2741b5dba622e35c40d63d1a82d54519 Mon Sep 17 00:00:00 2001 From: Rakesh Navale Date: Wed, 14 Oct 2026 18:24:26 +0100 Subject: [PATCH 6/6] Align Business Applications discovery contract Validate schema-driven search_paths inputs instead of requiring hard-coded query or filter wording that the hardened guidance intentionally removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/workiq-guidance/static.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/workiq-guidance/static.test.mjs b/tests/workiq-guidance/static.test.mjs index d6c061e..50d92c6 100644 --- a/tests/workiq-guidance/static.test.mjs +++ b/tests/workiq-guidance/static.test.mjs @@ -109,7 +109,7 @@ test('Business Applications discovery and inventory contracts stay aligned', () const action = fs.readFileSync(path.join(directory, 'references', 'do-action-work-iq.md'), 'utf8'); assert.match(skill, /Business Applications|CRM, ERP, or Power Apps/i); - assert.match(business, /search_paths[\s\S]{0,300}(?:query|filter)/i); + assert.match(business, /search_paths[\s\S]{0,300}fields? accepted by[\s\S]{0,80}(?:tool )?schema/i); assert.match(business, /fetch[\s\S]{0,80}\/businessapps\/environments\//i); assert.match(business, /discovery is read-only[\s\S]{0,100}search_paths[\s\S]{0,80}not `?do_action`?/i); assert.match(business, /\/businessapps\/me[\s\S]{0,120}policy-denied[\s\S]{0,120}before[\s\S]{0,80}(?:grounded path|search_paths|fetch)/i);