# Validation and Errors

> Section script policy capability codes, MCP compiler and publish checks for section JS, and how to fix each one.

Canonical URL: https://77865786950d:3005/scripting/validation

Section JS is checked in three places:

1. **The section script policy** (`@lexsis/section-script-policy`) runs in the
   MCP validator and again in the renderer before a script executes. In the
   renderer a blocked script is skipped and the section's content is revealed.
2. **The MCP compiler** rejects unsafe patterns when page or cart-module source
   is compiled.
3. **Publish preflight** checks literal `lifecycle.fetch` URLs against the
   store's network rules.

## Policy capability codes

Messages look like
`Blocked capability "<code>" at <line>:<column>: <message> (policy 1.0.0)`.

| Code | Blocks | Use instead |
|---|---|---|
| `network.fetch` | `fetch(` not written as `lifecycle.fetch(` / `motion.fetch(` | [`lifecycle.fetch`](/scripting/network) |
| `network.xml-http-request` | `XMLHttpRequest` | `lifecycle.fetch` |
| `network.web-socket` | `WebSocket` | Not available |
| `code.eval` | `eval(` | Plain code |
| `code.function-constructor` | `new Function(` | Plain code |
| `code.dynamic-import` | `import(` | Page-level `scripts`, an island, or managed motion |
| `navigation.window-location` | `window.location` | Links (`<a href>`) |
| `session.document-cookie` | `document.cookie` | [`lifecycle.storage`](/scripting/storage) |
| `storage.local` | `localStorage` | `lifecycle.storage.local` |
| `storage.session` | `sessionStorage` | `lifecycle.storage.session` |
| `messaging.post-message` | `postMessage` | Not available |
| `worker.dedicated` | `new Worker` | Not available |
| `worker.shared` | `SharedWorker` | Not available |
| `worker.import-scripts` | `importScripts` | Not available |
| `dom.document-write` | `document.write` | DOM methods |
| `dom.unsafe-inner-html` | `innerHTML =` with a non-literal value | `textContent` or `createElement` |

The patterns are textual: a blocked word in a comment or string still matches.

## Compiler checks

| Check | Trigger | Fix |
|---|---|---|
| `global_dom_query` | `document.querySelector`, `querySelectorAll`, `getElementById` or `getElementsByClassName` | `lifecycle.query` / `section.querySelector` |
| `unmanaged_mutation_observer` | `new MutationObserver(` | [`lifecycle.observeMutations`](/scripting/timers-and-observers#lifecycleobservemutations) |
| `unmanaged_animation_loop` | `setInterval(` or `requestAnimationFrame(` in a script that never references `lifecycle.` / `motion.` | `lifecycle.interval`, `lifecycle.frame` or `lifecycle.loop` |
| `programmatic_click` | `.click(` on a DOM control | Drive islands through their events and props |
| `unsafe_custom_commerce` | Commerce signals (`/cart/add`, variant IDs, quick-add) combined with custom DOM injection | QuickAdd, BuyBox, ProductCard actions and cart commands |

## Cart command checks

| Check | Trigger |
|---|---|
| `direct_cart_mutation` | Shopify cart mutations, `/cart/add.js`, `/cart/change.js`, `/cart/update.js`, or a Storefront access token in the script |
| `undocumented_cart_event` | Dispatching an `lx:cart:*` event that isn't a documented command, or a response event such as `lx:cart:add-items:success` |
| `cart_command_scope` | The command isn't dispatched from `section` (pages) or `root` (cart modules) |
| `cart_command_not_bubbling` | The event doesn't set `bubbles: true` |
| `cart_command_invalid_literal` | Missing `detail`, missing required fields, or an invalid literal such as a `requestId` outside 1–128 characters |
| `hidden_root_section` (warning) | A cart module's top-level `<section>` is authored `hidden` |

See [Cart API](/scripting/cart#cart-commands) for the commands.

## Publish checks

| Check | Severity | Trigger |
|---|---|---|
| `section_network` | Error | A literal `lifecycle.fetch` URL that no network rule allows, or `lx:connection/<slug>` with no such connection |
| `section_network` | Warning | A dynamic `lifecycle.fetch` URL (checked only at runtime) |

The MCP validator also warns about literal `lifecycle.storage` keys that break
the [key format](/scripting/storage#keys-and-limits).

## Runtime errors

| Message | Cause |
|---|---|
| `Section listeners cannot target outside the section or inside an island` | `lifecycle.on` with an out-of-scope node |
| `Section observers cannot target outside the section or inside an island` | `observeMutations` with an out-of-scope node |
| `Section animation loop limit (2) exceeded` | A third `lifecycle.loop` |
| `Cart currency or prices are not available yet; …` | Reading `lifecycle.cart` too early |
| `LxNetworkBlockedError` | A refused [`lifecycle.fetch`](/scripting/network#lifecyclefetch) |
| `[lx storage] …` warning | A refused [`storage.set`](/scripting/storage) |
| `[js] Disabled animation in <section>: <reason>` | The [watchdog](/scripting/timers-and-observers#watchdog) stopped the section |
