> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracing a store problem the monitor detected

> Glood's default store-monitor skill, word for word, ready to copy

The `store-monitor` skill as Glood ships it. The Store monitor template attaches it. Other agents get it only when you attach it.

> [!NOTE]
>
> These copies match the skill library in the Glood platform as of 30 October 2026 (revision `2135694e`). To put it back on a skill you edited, see
> [Restore a default skill](https://docs.glood.ai/platform/skills-library/index.md#restore-a-default-skill).

Each block below is one field of the skill editor. Use the copy button on each
block, and paste the body into the editor's **Markdown** view.

## Title

```text theme={null}
Tracing a store problem the monitor detected
```

## Slug

Fixed once a skill exists. You need it only when creating a new skill from this
text.

```text theme={null}
store-monitor
```

## Use when

```text theme={null}
use when a run was triggered by monitoring.anomaly_detected, a monitor insight (key starting monitor.) is open and you are asked why, or you run a scheduled review of this store
```

## Body

````markdown theme={null}
Glood's monitor checks this store every hour against its own normal range and opens an insight when
something breaks. It has already done the arithmetic. Your job is the part it cannot do: say **why**,
with evidence, in one finding per cause. Read the `rca` skill for the method; this one is the map.

## 1. How a run works

The first message ends with a line like `Triggered by monitoring.anomaly_detected: insight 812, insight 813.`

1. Call `list_insights` with `limit: 50` (open is the default) and take the insights named. Each carries `checkKey`, `severity` and `evidence`: `measure`, `window` (`from`, `to`, `grain`), `current`, `baseline` (the expected value), `delta`, `deltaPct`, `sampleSize`, and `values` with `lower` and `upper` (the normal range), `window` (`hour` or `day`) and `dayType`. A state check (section 3, second half) puts its facts in `values` instead: `url`, `outcome`, `httpStatus`. Rates are fractions: 0.021 is 2.1%.
2. **Several insights in one run are usually one incident.** Trace the most upstream first, in this order: store unreachable or password page, then traffic or bots, then speed or page errors, then add to cart or a best seller unavailable, then checkout, then conversion, then orders. A downstream insight explained by an upstream cause goes in the same finding, not a second one.
3. No tool returns a fingerprint (`list_findings` and `get_finding` give titles, not fingerprints), so you cannot read one back: build it every time from the cause vocabulary in section 5. The same cause on the same subject then bumps the open episode instead of opening a second one.
4. You have about twelve turns. Call independent reads together in one turn. If a query is refused, read the refusal and `read_skill` `query-language`; do not try variations blind.

**Windows.** Compare the breach with the same hours a week earlier, as two queries.

- `hour` window: now is `"since": "-3h", "until": "today"`; a week earlier is
  `"since": "-171h", "until": "-168h"`. The trend is `"over": "hour", "since": "-48h", "until": "today"`.
- `day` window (the day that just ended): `-Nd` counts today as day one, so yesterday is
  `"since": "-2d", "until": "-2d"` and the same weekday before it is `"since": "-9d", "until": "-9d"`.
- Probe measures are sparse (a visit every few hours): compare `"since": "-2d", "until": "today"`
  with `"since": "-9d", "until": "-3d"`. Those windows differ in length, so compare probe counts
  (`probeExceptions`, `probeConsoleErrors`, `probeFailedRequests`) per visit: divide by `probeVisits`.
- `get_changelog` takes ISO times with an offset: from a day before the window starts to its end.
- `get_changelog` times are UTC and `query` buckets are in the store's timezone (`get_store` `timezone`): convert a change's instant to that zone before placing it in a query window.

## 2. Rule out the measurement before the store

A broken measurement looks like a broken store. Check it first whenever a `storefront*` measure fell.

```jsonc
{ "select": ["storefrontEvents", "storefrontSessions", "historyOrders"], "over": "hour", "since": "-48h", "until": "today" }
```

`storefront*` at zero while `historyOrders` continues and the probe still loads the store (below)
means Glood's pixel stopped, not the store. File that as the finding, fingerprint
`monitor:pixel-stopped:store`, title "Storefront data stopped arriving", and nothing about shoppers.

```jsonc
{ "select": ["probeVisits", "probeVisitsOk", "probeUptime"], "by": ["pageType"], "since": "-2d", "until": "today" }
```

The probe is opt-in. With no probe visits at all (browser checks off), `historyOrders` continuing while
`storefront*` reads zero is enough to file it.

- `covered: false` is no data, not zero. Never file a drop on it.
- `storefrontOrders` is what the pixel saw at checkout, online store only; `historyOrders` is Shopify's
  own order records, every channel. A gap between them is channel mix, not a fault.
- A grouping returns one row keyed null for documents without the field. That row is real.

## 3. The branches

One section per check. Each names the measure the monitor judged, what to test, the queries, and the
cause names for the fingerprint.

**Reads every branch reuses.**

- **Changes:** `get_changelog` over the window with `subjectType` and `field`: `product` (`price`,
  `listPrice` for compare-at, `status`, `inStock`, `available`, `inventoryQty`, `deletedAt`),
  `discount` (`status`, `value`, `valuePercent`, `endsAt`, `combinesWith`, `usageLimit`,
  `deletedAt`), `theme` (`content` is a theme file edit, `enabled` an app embed switched on or off),
  `storefront` (`themeId`, `publishedAt`: a theme publish), `shop` (`planName`, `primaryDomain`,
  `currencyCode`, `ianaTimezone`), `market`. Add `subjectId` to drill into one product or discount.
- **Products:** `get_product` with a product id gives its title, handle, status, whether it is in stock and its price, from the store's catalogue (Glood's own when the store has no Glood app): name a product by its title, never by its id alone. When it changed: `status`, `inStock` and `available` in `get_changelog`.
- **Who:** the admin feed names the staff member or app behind a change.

```jsonc
{ "select": ["adminChanges"], "by": ["adminAuthor", "adminApp", "adminAction"], "where": [{ "field": "adminSubjectId", "op": "eq", "value": "<product or discount id>" }], "since": "-48h", "until": "today" }
```

  `adminAuthor` is the staff member or app Shopify names for the change; `adminApp` is the app or
  surface it came through, and an app's own title there means that app made it. No author is a
  system event, and order, customer and draft order events never carry one. An edit to a product or
  variant (price, compare-at, title) has no event at all: the feed holds a product's create, publish,
  unpublish and delete only, so for a price change say who made it is not recorded, never "a system
  change".
- **Probe errors:** which error, on which page. Then the same with `consoleError` and
  `failedRequestUrl`, and with `"by": ["pageType"]` and a `where` on the message.

```jsonc
{ "select": ["probeVisits"], "by": ["exceptionMessage"], "since": "-2d", "until": "today", "orderBy": { "key": "probeVisits", "dir": "desc" }, "limit": 5 }
```

### `monitor.traffic_drop`

`storefrontSessions` below its range. Section 2 first, then any open `monitor.store_unreachable` or
`monitor.password_page_on`: if one is open, that is the cause. Otherwise split by source, now against a
week earlier, then the same with `utmSource`, `country` and `device`.

```jsonc
{ "select": ["storefrontSessions"], "by": ["referrerHost"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontSessions", "dir": "desc" }, "limit": 15 }
```

One referrer or `utmSource` gone: a campaign ended or a link broke (`utmCampaign` names it). One
country: a market change (`get_changelog` `market`). One device: delivery, so read the probe by
`device`. Spread evenly: demand, or a domain change (`shop` `primaryDomain`). Causes:
`campaign-ended`, `referrer-lost`, `market-change`, `domain-change`.

### `monitor.bot_spike`

`storefrontBotEvents` above its range. Bot measures need `"includeBots": true`. Who, then where:

```jsonc
{ "select": ["storefrontBotEvents", "storefrontEvents", "storefrontBotShare"], "by": ["userAgent"], "since": "-3h", "until": "today", "includeBots": true, "orderBy": { "key": "storefrontBotEvents", "dir": "desc" }, "limit": 10 }
```

Then `"by": ["ipHash"]` and `"by": ["path"]`, and events per session per source:

```jsonc
{ "select": ["storefrontEventsPerSession", "storefrontSessions"], "by": ["ipHash"], "since": "-3h", "until": "today", "includeBots": true, "orderBy": { "key": "storefrontSessions", "dir": "desc" }, "limit": 10 }
```

One crawler's user agent: harmless unless the store slowed. A few `ipHash` with hundreds of events per
session: a scraper or an attack. It is an attack worth a critical finding only when the store suffered:
`probeTtfbP75` up or `probeUptime` down over the same days. Payment errors rising with it is card
testing (`monitor.payment_errors_spike`). Causes: `crawler`, `scraper`, `attack`.

### `monitor.conversion_drop`

`storefrontConversionRate` (`storefrontOrders` over `storefrontSessions`) below its range. Work the
`rca` ladder on the store's own funnel at hour grain:

```jsonc
{ "select": ["storefrontSessions", "storefrontProductViews", "storefrontAddToCartSessions", "storefrontCheckoutsStarted", "storefrontOrders", "storefrontConversionRate"], "over": "hour", "since": "-48h", "until": "today" }
```

Each stage over the one above it; the stage whose ratio broke is the stage that broke, and its branch
below is the trace. Orders down while checkouts started held: compare `historyOrders` for the same
hours. If the records held, the pixel lost `checkout_completed` (measurement, section 2); if they fell
too, read `monitor.payment_errors_spike` and `monitor.checkout_errors_spike`.

### `monitor.add_to_cart_drop`

`storefrontAddToCartRate` (sessions with an add to cart over sessions) below its range. Which product:

```jsonc
{ "select": ["storefrontProductViews", "storefrontAddToCarts"], "by": ["productId"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontProductViews", "dir": "desc" }, "limit": 15 }
```

Against a week earlier. A product whose views held while its adds collapsed is the subject: read its
`price`, `listPrice`, `status`, `inStock` and `available` in `get_changelog` with its `subjectId`,
then who changed it. A drop on every product: a store-wide price or compare-at episode, a discount that
ended (`discount`), a theme file edit or app embed (`theme`), or a page that slowed (the speed branch).
Split by `device` too: one device is delivery. The probe's journey says whether add to cart works at all:

```jsonc
{ "select": ["probeJourneys", "probeAddToCartWorked", "probeCheckoutReached"], "over": "day", "since": "-7d", "until": "today" }
```

Causes: `price-error`, `compare-at-removed`, `out-of-stock`, `product-unavailable`,
`discount-ended`, `theme-edit`, `app-embed`, or the speed branch's cause for a slow page.

### `monitor.checkout_drop`

`storefrontCheckoutRate` (checkouts started over sessions with an add to cart) below its range.

```jsonc
{ "select": ["storefrontAddToCartSessions", "storefrontCheckoutsStarted", "storefrontCheckoutRate", "storefrontExtensionErrors"], "by": ["device"], "since": "-3h", "until": "today" }
```

Extension errors above zero: which checkout extension failed, and how. The target is the extension
point it renders at (`purchase.checkout.block.render`), the type its error class: name both.

```jsonc
{ "select": ["storefrontExtensionErrors"], "by": ["checkoutErrorTarget", "checkoutErrorType"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontExtensionErrors", "dir": "desc" }, "limit": 10 }
```

Then the probe journey query above, and the probe errors on `cart` and `checkout` pages. Suspects: a
theme edit to the cart (`theme` `content`), an app embed switched on (`enabled`), a checkout extension
failing (`storefrontExtensionErrors`), a cart discount that ended. Shipping and checkout settings are
not recorded anywhere you can read: say so if nothing else explains it. Causes: `checkout-unreachable`,
`checkout-extension`, `theme-edit`, `app-embed`, `discount-ended`.

### `monitor.checkout_errors_spike`

`storefrontCheckoutErrors` above its range. Which error, on which field:

```jsonc
{ "select": ["storefrontCheckoutErrors"], "by": ["checkoutErrorType", "checkoutErrorTarget"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontCheckoutErrors", "dir": "desc" }, "limit": 15 }
```

`inventory_error` or `merchandise_error`: stock (`inStock`, `inventoryQty`; overselling). `discount_error`:
a code ended, was deleted or hit its limit (`discount`; then `"by": ["discountCode"]`). `delivery_error`:
shipping zones or rates, which are not recorded: say so. `input_invalid` or `input_required` on one
target: a checkout field or extension (`storefrontExtensionErrors`, app embeds). `payment_error`: the
next branch. Causes: `inventory-error`, `discount-error`, `delivery-error`, `checkout-field`.

### `monitor.payment_errors_spike`

`storefrontPaymentErrors` above its range. Card testing is many failures from few sources:

```jsonc
{ "select": ["storefrontPaymentErrors", "storefrontSessions"], "by": ["ipHash"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontPaymentErrors", "dir": "desc" }, "limit": 10 }
```

Then `"by": ["paymentMethod"]`. From the order records, which gateway and which code:

```jsonc
{ "select": ["historyOrders", "historyFailedPaymentOrders"], "by": ["gateway", "paymentErrorCode"], "since": "-2d", "until": "today" }
```

Few `ipHash` with many failures each: card testing. One gateway or one `paymentErrorCode` across many
shoppers: that gateway failing. A gateway that sold every day and now sells nothing (`historyOrders` by
`gateway`, `"over": "day", "since": "-15d"`): a payment method removed. Causes: `card-testing`,
`gateway-failing`, `payment-method-removed`.

### `monitor.orders_drop`

`historyOrders` below its range, every channel. Zero orders in a window that normally holds five or
more fires on one window: the loudest signal there is. First any open upstream insight (unreachable,
password page, add to cart, checkout): that is the cause. Then section 2. Then split, now against a
week earlier:

```jsonc
{ "select": ["historyOrders", "historyRevenue"], "by": ["referrerHost"], "since": "-3h", "until": "today", "orderBy": { "key": "historyOrders", "dir": "desc" }, "limit": 15 }
```

and with `country` and `gateway`. The storefront funnel normal while the records fell: a channel
outside the online store stopped, or a gateway (previous branch). Causes: `no-orders`, plus any
upstream cause.

### `monitor.orders_spike`

`historyOrders` above its range. Which code and where from:

```jsonc
{ "select": ["historyOrders", "historyAov", "historyDiscountedOrders", "historyHighRiskOrders"], "by": ["discountCode"], "since": "-3h", "until": "today", "orderBy": { "key": "historyOrders", "dir": "desc" }, "limit": 10 }
```

and `"by": ["referrerHost"]`, `"by": ["productId"]`. One code: the discount check below. One product
at a lower AOV: a price reduced by mistake (`get_changelog` `price`, an episode of many products is one
repricing). One referrer or campaign with normal prices: a promotion working, an `info` finding at most.
Causes: `leaked-code`, `internal-code`, `price-error`, `campaign`.

### `monitor.discount_usage_spike`

`historyDiscountedShare` (discounted orders over orders) above its range.

```jsonc
{ "select": ["historyOrders", "historyDiscountedOrders", "historyDiscountDepth"], "by": ["discountCode"], "since": "-3h", "until": "today", "orderBy": { "key": "historyDiscountedOrders", "dir": "desc" }, "limit": 10 }
```

Then call `run_checks` with resource `discount` once (the fraud agent's deterministic signals, usable by
any agent). Page 1 holds the 25 codes most used this week; call `page: 2` only when the spiking code is
not on it, never page through `overCap`. Per code it returns `codeLeakStatus`
(normal, spike, leaked: a spike plus first-time buyers or coupon-site referrers) and
`internalDiscountAnomaly` (a high-value code used by a few buyers). `discountsKnown` false means the
store has not granted discount access, so value and creator are unknown: say so. Quote its `signals`;
never re-derive them. **Ignore its `defaultActions` and `policySkill`**: they are the fraud agent's.
Do not follow fraud-policy and never record its `code-leak:` or `internal-discount:` fingerprints;
file only the monitor finding below, so one cause raises one alert. Who created the code:

```jsonc
{ "select": ["adminChanges"], "by": ["adminAuthor", "adminApp"], "where": [{ "field": "adminSubjectType", "op": "in", "value": ["discountCodeNode", "discountAutomaticNode", "discountAutomaticBxgy", "discountNode", "priceRule"] }, { "field": "adminAction", "op": "eq", "value": "create" }], "since": "-30d", "until": "today" }
```

Stacking: a `combinesWith` change in `get_changelog` `discount`. A new code or a raised `value` in the
window. Causes: `leaked-code`, `internal-code`, `stacking`, `test-code-active`.

`run_checks` stores an assessment. On a deployment in observe mode, `record_finding` refuses every
finding for the rest of a run that called it, so in observe mode skip `run_checks` and trace with the
queries alone.

### `monitor.zero_result_search_rise`

`storefrontZeroResultRate` (zero-result searches over searches) above its range.

```jsonc
{ "select": ["storefrontZeroResultSearches", "storefrontSearches"], "by": ["searchTerm"], "since": "-3h", "until": "today", "orderBy": { "key": "storefrontZeroResultSearches", "dir": "desc" }, "limit": 15 }
```

Many different terms with no results: search itself broke (`monitor.search_broken`, a search app embed
or a theme search template edit in `theme`). A few terms: the products that answered them became
unavailable (`status`, `inStock`, `deletedAt`, `publishedAt`; `get_product`), or shoppers want
something the store does not sell (an `info` finding naming the terms). Causes: `search-broken`,
`product-unavailable`, `unmet-demand`.

### `monitor.speed_regression`

`probeLcpP75` (75th percentile largest paint, ms) above its range. Which pages and devices:

```jsonc
{ "select": ["probeVisits", "probeLcpP75", "probeTtfbP75", "probeTbtP75", "probeLongTaskMsAvg", "probeJsHeapMax"], "by": ["pageType", "device"], "since": "-2d", "until": "today" }
```

Against `-9d`..`-3d`, then the weight of a visit, the same two windows:

```jsonc
{ "select": ["probeTransferBytesAvg", "probeRenderBlockingBytesAvg", "probeRequestsAvg", "probeThirdPartyRequestsAvg"], "by": ["pageType"], "since": "-2d", "until": "today" }
```

and which domains a visit calls, a domain present now and absent before being a new script:

```jsonc
{ "select": ["probeVisits"], "by": ["requestDomain"], "since": "-2d", "until": "today", "orderBy": { "key": "probeVisits", "dir": "desc" }, "limit": 25 }
```

TTFB alone up: the server side (bot load, Shopify), not the theme. Blocking time and long tasks up:
script (an app embed switched on, a theme file edit). Render-blocking bytes up: a theme asset. Then
`theme` and `storefront` in `get_changelog` for the window. Causes: `theme-edit`, `theme-publish`,
`app-embed`, `third-party-script` (a new domain no embed change explains), `slow-server`.

### `monitor.page_errors_spike`

`probeConsoleErrors` plus `probeExceptions` above their range. Or, on a store with no probe range for
the hours (under 14 days of probe history, or between a day range's midnights), the latest probe run
threw an uncaught exception on at least half of its visits: then `evidence.evaluator` is `structural`
and `values` holds `exceptions` (the messages, one per line), `urls` (the pages that threw),
`failingVisits` of `visits`, and `firstSeenAt`, the first probe visit of the last three days that
threw it. That is the first failing probe: the change you are looking for was made before it. Which
pages (per visit, as above):

```jsonc
{ "select": ["probeVisits", "probeExceptions", "probeConsoleErrors", "probeFailedRequests"], "by": ["pageType"], "since": "-2d", "until": "today" }
```

Then the probe errors read (`exceptionMessage`, `consoleError`, `failedRequestUrl`), and
`"over": "hour"` on `probeExceptions` for the first hour it threw when `firstSeenAt` is absent. Look for
a theme or app change before the first failing probe: `theme` (a file edit, an app embed) and
`storefront` (a theme publish) in `get_changelog` from a day before it up to it. A theme edit whose diff
adds the throwing script names the file and the line. A failing URL on an app's domain names the app;
a 4xx or 5xx on the store's own API is Shopify or a theme call; an error that starts after a theme file
edit or app embed is that change. Causes: `theme-edit`,
`app-embed`, `third-party-script`, `failing-request`, `script-error` (none of those explains it).

### `monitor.fulfilment_delay`

`historyFulfilmentHours` (mean hours from order to fulfilment, dated by fulfilment) above its range.
Which carrier and which item:

```jsonc
{ "select": ["historyFulfilledOrders", "historyFulfilmentHours"], "by": ["carrier"], "since": "-2d", "until": "-2d" }
```

against `"since": "-9d", "until": "-9d"`, then `"by": ["variantSku"]` and `"by": ["trackingStatus"]`.
The backlog by day of order: `historyUnfulfilledOrders` `"over": "day", "since": "-15d"`. One SKU
waiting: stock, so `get_product` and `inventoryQty` in `get_changelog` (a negative or zero quantity
with orders still coming in is overselling). Every item and carrier slower: the warehouse. Orders held
for review: `historyHighRiskOrders`. Causes: `oversold`, `fulfilment-backlog`, `carrier-delay`.

### `monitor.returns_spike`

`historyReturns` (dated by the return) above its range. Why, and what:

```jsonc
{ "select": ["historyReturns"], "by": ["returnReason"], "since": "-2d", "until": "today", "orderBy": { "key": "historyReturns", "dir": "desc" }, "limit": 10 }
```

then `"by": ["variantSku"]` and `"by": ["carrier"]`. Damaged and one carrier: transit. Wrong item: a
SKU mapping (`sku` in `get_changelog`). One product's sizing or description: the product page.
Causes: `returns`, `damaged-in-transit`, `wrong-item`.

### `monitor.refunds_spike`

`historyRefundAmount` (dated by the refund) above its range.

```jsonc
{ "select": ["historyRefunds", "historyRefundAmount"], "by": ["productId"], "since": "-2d", "until": "today", "orderBy": { "key": "historyRefundAmount", "dir": "desc" }, "limit": 10 }
```

then `"by": ["returnReason"]` and `"by": ["gateway"]`. Then how long that product's orders took to ship, against the store's (a store-wide mean hides a few late orders):

```jsonc
{ "select": ["historyFulfilledOrders", "historyFulfilmentHours"], "by": ["productId"], "where": [{ "field": "productId", "op": "eq", "value": "<product id>" }], "since": "-30d", "until": "today" }
```

Refunds of orders that shipped days late are `late-shipping`. Refunds with returns follow the returns branch. Causes: `refunds`, `late-shipping`.

### `monitor.store_unreachable`

State check, critical: the probe's home visit ended `httpError` (500 or more), `timeout` or `blank`.
`values` holds `url`, `outcome`, `httpStatus`.

```jsonc
{ "select": ["probeVisits", "probeVisitsOk"], "by": ["pageType", "probeOutcome", "httpStatus"], "since": "-2d", "until": "today" }
```

Every page failing: the store or Shopify (status 5xx), or the domain (`shop` `primaryDomain`; DNS is
not observable). Home only: the home template. `blank` (answered, painted nothing): a theme file edit or
publish (`theme`, `storefront`). Shoppers still arriving (`storefrontSessions` by hour) while the probe
fails: the probe's own region or path, not the store (`probe-only`). A paused or frozen plan
(`shop` `planName`) shows a password page instead. Causes: `store-down`, `theme-edit`, `theme-publish`,
`domain-change`, `probe-only`.

### `monitor.password_page_on`

State check, critical: the probe met the password page. When it started:

```jsonc
{ "select": ["probeVisits", "probePasswordPageVisits"], "over": "day", "since": "-7d", "until": "today" }
```

Shopify's admin feed records no event for the password setting itself, so who switched it on is
usually unknown: read `adminChanges` `"by": ["adminAuthor", "adminApp", "adminAction"]` over the hours
before (a lead, not proof), and `shop` `planName` (a plan change can force it). A store that keeps it on
by design turns this check off in its check settings: say so in `recommend`. Cause: `password-page`.

### `monitor.add_to_cart_broken`

State check, critical: the probe's journey could not add its product to the cart. The journey query
(add to cart branch), then the errors on the pages it walks. The journey visit itself is stored as
`checkout` (it starts on the product page), so filter all three:

```jsonc
{ "select": ["probeVisits"], "by": ["exceptionMessage"], "where": [{ "field": "pageType", "op": "in", "value": ["product", "cart", "checkout"] }], "since": "-2d", "until": "today", "orderBy": { "key": "probeVisits", "dir": "desc" }, "limit": 5 }
```

Then shoppers: if `storefrontAddToCarts` by hour held, only the probe's product fails, so its
`status`, `inStock`, `available` (`out-of-stock`, `product-unavailable`), or nothing wrong with it
(`probe-only`). If shoppers' adds fell too, a theme file edit or app embed in the window. Causes:
`add-to-cart-broken`, `theme-edit`, `app-embed`, `out-of-stock`, `product-unavailable`, `probe-only`.

### `monitor.checkout_unreachable`

State check, critical: add to cart worked and checkout did not load. The journey query, the probe
errors on `checkout` pages, `storefrontCheckoutsStarted` by hour, `storefrontExtensionErrors`, and
app embeds or cart edits in `theme`. Shoppers still starting checkouts: `probe-only`. The probe sees
only the first checkout page. Causes: `checkout-unreachable`, `checkout-extension`, `theme-edit`,
`app-embed`, `probe-only`.

### `monitor.search_broken`

State check, warning: the probe's search page rendered no products. Shoppers' searches
(`storefrontZeroResultRate` by hour) normal: the probe's term matched nothing (`probe-term`, an `info`
finding at most). Abnormal too: a search app embed or theme search template change (`theme`), or the
products unpublished. Causes: `search-broken`, `theme-edit`, `app-embed`, `product-unavailable`,
`probe-term`.

### `monitor.bestseller_unavailable`

State check, warning, one insight per product (its subject is the product id): a top seller of the
last 30 days whose catalogue status is not active or that has no variant available. `values` holds
`status` and `availableVariants`. `get_changelog` with its `subjectId` says what changed and when; the
admin feed with `adminSubjectId` says who (inventory changes carry no author). Archived on purpose
(seasonal, discontinued): an `info` finding saying so. For impact quote its orders:

```jsonc
{ "select": ["historyOrders"], "by": ["productId"], "where": [{ "field": "productId", "op": "eq", "value": "<product id>" }], "since": "-30d", "until": "today" }
```

`historyRevenue` by `productId` is the whole total of orders containing it, not the product's share:
never quote it as the product's revenue. Causes: `product-unavailable`, `out-of-stock`.

### `monitor.tls_expiring`

State check, nothing to trace: the insight and its alert are the whole answer. Asked about it: Shopify
renews certificates on its own domains; a custom domain whose records no longer point at Shopify
cannot renew. `probeTlsDaysLeft` by day shows the countdown.

### `monitor.crawler_access_expiring`

State check, nothing to trace. Glood's crawler access signature, set on the store's Monitoring tab,
expires at `values.expiresAt`; after it the probe is blocked and every probe check goes quiet. The
merchant renews it there.

## 4. Confirm before filing

The `rca` rule holds: a suspect is a cause only when its time brackets the break (before it, not
after) **and** its subject matches the segment (the product, device, page or code that moved). Timing
without subject is a coincidence: name it in `ruledOut`, never as the cause.

## 5. The finding

One `record_finding` per cause, naming every insight it explains.

- One finding per root cause. Never file one for a measure within its normal range, or one you only read while ruling suspects out: that goes in `ruledOut`.

- **fingerprint** `monitor:<cause>:<subject>`, all lowercase, the cause and its subject from the table
  below and nothing else. For example `monitor:price-error:7781`. Never a date or a figure.
- **title** names the cause: "Oxford Shirt XL repriced to 24,990 by a repricing app".
- **because** carries the chain: what moved and by how much (from the insight), the stage and segment,
  the suspect with its time and who made it, and the test that confirmed it.
- **evidence**: `insightIds` (the insights explained, up to 10), `facts` (up to 20, each a `label`, a
  `value` as text and the `source` tool or query that gave it), `ruledOut` (up to 10, each a `suspect`
  and `because`).
- **severity**: the insight's, unless the trace shows otherwise (a harmless crawler, a deliberate
  archive, a campaign working: `info`).
- **impactMinor** only from a figure you read, never an estimate. If this agent's soul has a money bar,
  `record_finding` refuses a `warning` without it: give one you read, or file `info`. **recommend**:
  the merchant's next step.

No cause confirmed: file one finding anyway, `monitor:unexplained:<check>`, saying what moved and every
suspect in `ruledOut`, so the merchant is told what was checked. Never promote a coincidence to fill
the gap.

The cause vocabulary. Every branch names its causes from this table; the subject is fixed per cause.

| Subject | Causes |
| --- | --- |
| `store` | `pixel-stopped`, `store-down`, `domain-change`, `password-page`, `crawler`, `scraper`, `attack`, `slow-server`, `add-to-cart-broken`, `checkout-unreachable`, `checkout-extension`, `inventory-error`, `delivery-error`, `card-testing`, `no-orders`, `search-broken`, `probe-term`, `fulfilment-backlog`, `late-shipping` |
| product id (`store` when one change-log episode spans many products) | `price-error`, `compare-at-removed`, `out-of-stock`, `product-unavailable`, `oversold`, `returns`, `wrong-item`, `refunds` |
| theme id | `theme-edit`, `theme-publish` |
| app handle, the first part of the change's scope id (`<app>/blocks/<block>`) | `app-embed` |
| request domain | `third-party-script`, `failing-request` |
| probe page type | `script-error`, `probe-only` |
| discount id | `discount-ended` |
| discount code | `discount-error`, `leaked-code`, `internal-code`, `stacking`, `test-code-active` |
| `utmCampaign`, else `utmSource`, else referrer host | `campaign`, `campaign-ended` |
| referrer host | `referrer-lost` |
| country | `market-change` |
| gateway | `gateway-failing`, `payment-method-removed` |
| carrier | `carrier-delay`, `damaged-in-transit` |
| checkout error target | `checkout-field` |
| search term | `unmet-demand` |
| check key without `monitor.` | `unexplained` |

## 6. What this data cannot see

Say the relevant one in `because` when it limits the answer.

- **Probe**: one region, every few hours (default 6), so its timings are trends, not absolute numbers.
  It stops at the first checkout page, does not measure dedicated workers or cross-site frames, filters
  out the `serviceWorker` SecurityError, and runs only on
  stores whose browser checks are switched on: without them there is no reachability, password, add
  to cart, checkout, search, speed or page-error detection.
- **Probe error groupings** (`exceptionMessage`, `consoleError`, `failedRequestUrl`) cover only visits
  since probe version 2, so the groups can sum to less than `probeExceptions`; counts and percentiles
  cover every visit.
- **Theme edits are polled**: an edit and its revert inside one poll interval leave no record.
- **Admin events**: order, customer and draft order events carry no author; inventory changes have
  none in Shopify's feed; there is no event for the password setting, nor for a product or variant
  edit (price, compare-at, title): the change log says what changed, never who.
- **Not observable at all**: app installs, DNS and resolved IP, Shopify's status, review text, card
  BIN, real-user performance, shipping and checkout settings.
- **Storefront measures need Glood's pixel** on the store. Without it there are no `storefront*`
  baselines and those checks stay silent; orders, probe and state checks still run.
- **Fulfilment location** is not recorded (the app is not granted Shopify's locations scope), so there
  is no `fulfillmentLocation` dimension: say a location cannot be named.
- **Order records**: new fields (payment, risk, fulfilment, refunds) fill in as orders change, so old
  orders may lack them. `historyOrders` and `historyUnfulfilledOrders` leave cancelled orders out; the
  fulfilled, returns and refunds measures count them. `historyDiscountDepthTotal` is a sum of fractions,
  meaningless alone: quote `historyDiscountDepth`.
- **Partner datasets** (the Glood apps' own) drop documents that lack a grouped field instead of
  returning a null row, so their groups can sum to less than the total.
````


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.