From 3c97635806ca3783ff33545da529b708e79510a3 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Tue, 28 Apr 2026 19:58:49 +0530 Subject: [PATCH 1/2] Flag server logic with-pattern issues - Detect raw with( substrings before server-side validation fails at runtime - Document OData startswith/endswith literal splitting workaround - Add tests for rejected and accepted startswith patterns Co-authored-by: GPT-5.5 <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/validate-serverlogic.test.js | 39 ++++++++++ .../skills/add-server-logic/SKILL.md | 75 +++++++++++++++++++ .../scripts/validate-serverlogic.js | 18 +++++ 3 files changed, 132 insertions(+) diff --git a/plugins/power-pages/scripts/tests/validate-serverlogic.test.js b/plugins/power-pages/scripts/tests/validate-serverlogic.test.js index fff243f2f..ee0046e2a 100644 --- a/plugins/power-pages/scripts/tests/validate-serverlogic.test.js +++ b/plugins/power-pages/scripts/tests/validate-serverlogic.test.js @@ -228,3 +228,42 @@ test('yml name mismatch is flagged', (t) => { assert.equal(result.status, 2); assert.match(result.stderr, /does not match folder name/); }); + +test('startswith( inside string literal is flagged (with-pattern)', (t) => { + const projectRoot = createTempProject(t); + setupProject(projectRoot); + const startsWithJs = `function get() { + try { + Server.Logger.Log("test-endpoint GET called"); + var query = "$filter=startswith(name,'INV-')"; + return JSON.stringify({ status: "success", query: query }); + } catch (err) { + Server.Logger.Error("test-endpoint GET failed: " + err.message); + return JSON.stringify({ status: "error", message: err.message }); + } +}`; + writeServerLogic(projectRoot, 'test-endpoint', startsWithJs, VALID_YML); + + const result = runValidator(projectRoot); + assert.equal(result.status, 2); + assert.match(result.stderr, /contains the substring 'with\('/); +}); + +test('split startswith( workaround passes validation', (t) => { + const projectRoot = createTempProject(t); + setupProject(projectRoot); + const splitJs = `function get() { + try { + Server.Logger.Log("test-endpoint GET called"); + var query = "$filter=startswith" + "(name,'INV-')"; + return JSON.stringify({ status: "success", query: query }); + } catch (err) { + Server.Logger.Error("test-endpoint GET failed: " + err.message); + return JSON.stringify({ status: "error", message: err.message }); + } +}`; + writeServerLogic(projectRoot, 'test-endpoint', splitJs, VALID_YML); + + const result = runValidator(projectRoot); + assert.equal(result.status, 0, result.stderr); +}); diff --git a/plugins/power-pages/skills/add-server-logic/SKILL.md b/plugins/power-pages/skills/add-server-logic/SKILL.md index b607d15d1..6a3f1e1d9 100644 --- a/plugins/power-pages/skills/add-server-logic/SKILL.md +++ b/plugins/power-pages/skills/add-server-logic/SKILL.md @@ -432,6 +432,26 @@ Repeat this step for each approved server logic item. Create or update `` in diagnostics, and the function silently falls through without executing user code. + +| Pattern | Regex | Caveat | +|---------|-------|--------| +| JavaScript `with` statement | `with\s*\(` | The regex matches the substring `with(` **anywhere** in the file — including inside string literals and inside other identifiers. OData filter functions like `startswith(`, `endswith(`, and `groupwith(` will trip it because they end with `with(`. | + +**Workaround for OData functions** — split the literal so `with(` is not contiguous in source: + +```javascript +// ❌ Triggers validator: "startswith(" contains the substring "with(" +var query = "$filter=startswith(name,'INV-')"; + +// ✅ Split the literal — server still receives "startswith(name,...)" +var query = "$filter=startswith" + "(name,'INV-')"; +``` + +The same trick applies to `endswith(`, `groupwith(`, and any other identifier that ends with `with(`. + #### Code Template ```javascript @@ -1112,6 +1132,7 @@ Provide testing instructions: Use the frontend integration reference from Phase 9 for the exact calling pattern that matches the site's stack. 5. **Check diagnostics** — Server.Logger output can be viewed in Power Pages design studio diagnostics +6. **If the endpoint returns an error or unexpected response** — see [Troubleshooting Server Logic Execution Errors](#troubleshooting-server-logic-execution-errors) for the Playwright + `X-Ms-UserTrace` debugging flow **Output**: Code validated, API URL provided, test guidance given @@ -1188,6 +1209,60 @@ After deployment (or if skipped), remind the user: --- +## Troubleshooting Server Logic Execution Errors + +When a deployed server logic endpoint returns an error or unexpected response, the underlying cause is usually hidden inside the `X-Ms-UserTrace` response header — a base64-encoded blob containing the runtime diagnostic logs (script-validation failures, sandbox exceptions, connector errors, timeout messages). The Power Pages design studio diagnostics view shows the same data, but inspecting the response header is the fastest path when iterating against a live site. + +Use this flow whenever a server logic call fails or returns a different response than expected: + +### 1. Open the Live Site in a Browser via Playwright + +Use the Playwright MCP tools (`mcp__plugin_power-pages_playwright__browser_navigate`, `browser_snapshot`, `browser_click`, etc.) to drive the site: + +1. Navigate to the deployed site URL (the `websiteUrl` returned by `/activate-site` or shown in the Power Pages admin center). +2. Sign in if the endpoint requires authentication. +3. Trigger the action that calls the failing server logic endpoint (click the button, submit the form, etc.) — or call the endpoint directly via `mcp__plugin_power-pages_playwright__browser_evaluate` with `fetch()`. + +### 2. Capture the Network Response + +Use `mcp__plugin_power-pages_playwright__browser_network_requests` to list network activity, then locate the request to `/_api/serverlogics/`. Note: + +- The HTTP status code (e.g., 200, 400, 500) +- The response body (often a generic error or empty payload when validation fails) +- **Most importantly: the `X-Ms-UserTrace` response header** — this is where the actual diagnostic logs live + +If `browser_network_requests` does not surface the response headers directly, fall back to `mcp__plugin_power-pages_playwright__browser_evaluate` and read the headers from a `fetch()` call: + +```javascript +const res = await fetch('/_api/serverlogics/', { method: 'GET', credentials: 'include' }); +const trace = res.headers.get('X-Ms-UserTrace'); +return { status: res.status, body: await res.text(), trace }; +``` + +### 3. Decode the `X-Ms-UserTrace` Header + +The header value is base64-encoded JSON. Decode it via Bash: + +```bash +echo '' | base64 -d +``` + +The decoded payload contains the diagnostic log entries — including the actual error message, the prohibited pattern (if script validation failed), the connector error (if Dataverse/HttpClient failed), or the stack trace (if the function threw at runtime). + +### 4. Common Errors and Fixes + +| Decoded message | Likely cause | Fix | +|-----------------|--------------|-----| +| `Script validation failed: prohibited pattern found - Pattern: with\s*\(` | Source contains the substring `with(` (often inside `startswith(` / `endswith(`) | See [Prohibited Script Patterns](#prohibited-script-patterns) in Phase 5 | +| `Script validation failed: prohibited pattern found - Pattern: ` | Other prohibited construct in the script | Inspect the regex, locate the matching substring, and rewrite to avoid it | +| Empty result set from `Server.Connector.Dataverse.RetrieveMultipleRecords` | Missing or insufficient table permissions | Re-run the table permissions setup (Phase 6) — Dataverse connector respects table permissions and silently returns 0 records when they are missing | +| HTTP 401 / 403 | Missing web role or anonymous access blocked by governance | Verify `adx_serverlogic_adx_webrole` in the metadata YAML and the user's web roles | +| HTTP 500 with stack trace in `X-Ms-UserTrace` | Runtime exception inside the function | Read the trace, fix the bug, redeploy via `/deploy-site` and clear cache | + +After fixing, redeploy via `/deploy-site` and restart the site so the change is picked up immediately. + +--- + ## Important Notes ### Throughout All Phases diff --git a/plugins/power-pages/skills/add-server-logic/scripts/validate-serverlogic.js b/plugins/power-pages/skills/add-server-logic/scripts/validate-serverlogic.js index f68e00dc6..aa975c310 100644 --- a/plugins/power-pages/skills/add-server-logic/scripts/validate-serverlogic.js +++ b/plugins/power-pages/skills/add-server-logic/scripts/validate-serverlogic.js @@ -202,6 +202,24 @@ runValidation((cwd) => { if (/(?:async\s+)?function\s+delete\s*\(/m.test(strippedContent)) { errors.push(`${dirName}.js: uses 'function delete()' — 'delete' is a reserved word, use 'del' instead`); } + + // Check: no 'with(' substring anywhere in the raw source. + // The Power Pages server validator rejects scripts matching /with\s*\(/ to block + // the JavaScript `with` statement, but the regex also matches the substring inside + // identifiers like `startswith(`, `endswith(`, and `groupwith(` — even inside string + // literals — which causes runtime error: "Script validation failed: prohibited + // pattern found - Pattern: with\s*\(". Run this on the raw content (not stripped) + // because the server validator does the same. + const withMatches = [...content.matchAll(/with\s*\(/g)]; + if (withMatches.length > 0) { + const lineNumbers = withMatches.map(m => content.slice(0, m.index).split('\n').length); + errors.push( + `${dirName}.js: contains the substring 'with(' on line(s) ${lineNumbers.join(', ')} — ` + + `the Power Pages server validator's regex /with\\s*\\(/ blocks this even inside string literals ` + + `(e.g., OData functions like startswith(, endswith(). Split the literal so 'with(' is not contiguous, ` + + `e.g., \`"startswith" + "(crd50_name,..."\` instead of \`"startswith(crd50_name,..."\`.` + ); + } } if (errors.length > 0) { From 95a7f52fc33e7024dab912f14f6972dd36f47075 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Tue, 28 Apr 2026 20:08:40 +0530 Subject: [PATCH 2/2] Update troubleshooting section in SKILL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 📝 Clarified the diagnostic log retrieval process. - 🔄 Removed specific references to the Power Pages design studio diagnostics view. - 🔍 Updated instructions for using Playwright MCP tools. - ❌ Removed unnecessary steps and streamlined the troubleshooting flow. -Priyanshu --- .../skills/add-server-logic/SKILL.md | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/plugins/power-pages/skills/add-server-logic/SKILL.md b/plugins/power-pages/skills/add-server-logic/SKILL.md index 6a3f1e1d9..c08b77f5c 100644 --- a/plugins/power-pages/skills/add-server-logic/SKILL.md +++ b/plugins/power-pages/skills/add-server-logic/SKILL.md @@ -1211,17 +1211,17 @@ After deployment (or if skipped), remind the user: ## Troubleshooting Server Logic Execution Errors -When a deployed server logic endpoint returns an error or unexpected response, the underlying cause is usually hidden inside the `X-Ms-UserTrace` response header — a base64-encoded blob containing the runtime diagnostic logs (script-validation failures, sandbox exceptions, connector errors, timeout messages). The Power Pages design studio diagnostics view shows the same data, but inspecting the response header is the fastest path when iterating against a live site. +When a deployed server logic endpoint returns an error or unexpected response, the underlying cause is usually hidden inside the `X-Ms-UserTrace` response header — a base64-encoded blob containing the runtime diagnostic logs. The Power Pages Edge browser extension shows the same data, but inspecting the response header is the fastest path when iterating against a live site. Use this flow whenever a server logic call fails or returns a different response than expected: ### 1. Open the Live Site in a Browser via Playwright -Use the Playwright MCP tools (`mcp__plugin_power-pages_playwright__browser_navigate`, `browser_snapshot`, `browser_click`, etc.) to drive the site: +Use the Playwright MCP tools to drive the site: 1. Navigate to the deployed site URL (the `websiteUrl` returned by `/activate-site` or shown in the Power Pages admin center). -2. Sign in if the endpoint requires authentication. -3. Trigger the action that calls the failing server logic endpoint (click the button, submit the form, etc.) — or call the endpoint directly via `mcp__plugin_power-pages_playwright__browser_evaluate` with `fetch()`. +2. Ask the user to sign in if the endpoint requires authentication and wait for confirmation. +3. Trigger the action that calls the failing server logic endpoint (click the button, submit the form, etc.) — or call the endpoint directly with `fetch()`. ### 2. Capture the Network Response @@ -1241,23 +1241,9 @@ return { status: res.status, body: await res.text(), trace }; ### 3. Decode the `X-Ms-UserTrace` Header -The header value is base64-encoded JSON. Decode it via Bash: +The header value is base64-encoded JSON. Decode it. -```bash -echo '' | base64 -d -``` - -The decoded payload contains the diagnostic log entries — including the actual error message, the prohibited pattern (if script validation failed), the connector error (if Dataverse/HttpClient failed), or the stack trace (if the function threw at runtime). - -### 4. Common Errors and Fixes - -| Decoded message | Likely cause | Fix | -|-----------------|--------------|-----| -| `Script validation failed: prohibited pattern found - Pattern: with\s*\(` | Source contains the substring `with(` (often inside `startswith(` / `endswith(`) | See [Prohibited Script Patterns](#prohibited-script-patterns) in Phase 5 | -| `Script validation failed: prohibited pattern found - Pattern: ` | Other prohibited construct in the script | Inspect the regex, locate the matching substring, and rewrite to avoid it | -| Empty result set from `Server.Connector.Dataverse.RetrieveMultipleRecords` | Missing or insufficient table permissions | Re-run the table permissions setup (Phase 6) — Dataverse connector respects table permissions and silently returns 0 records when they are missing | -| HTTP 401 / 403 | Missing web role or anonymous access blocked by governance | Verify `adx_serverlogic_adx_webrole` in the metadata YAML and the user's web roles | -| HTTP 500 with stack trace in `X-Ms-UserTrace` | Runtime exception inside the function | Read the trace, fix the bug, redeploy via `/deploy-site` and clear cache | +The decoded payload contains the diagnostic log entries — including the actual error message, the prohibited pattern (if script validation failed). After fixing, redeploy via `/deploy-site` and restart the site so the change is picked up immediately.