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

# Rule reference

> Settings, options, and behaviors for Rules in Sift.

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

A Rule defines logical conditions used to automatically evaluate telemetry data from one or more Channels during a Run. Built using the [Common Expression Language (CEL)](https://cel.dev), Rules can identify behaviors, anomalies, or thresholds in live or historical data streams.

## Capabilities

Rules can be created, previewed, and managed either through the Sift UI or programmatically using the Sift API. The following table describes the key capabilities of Rules:

<MintTable
  columns={['Capability', 'Description']}
  columnWidths={['25%', '75%']}
  rows={[
['Evaluation modes', 'Rules support both real-time and retrospective evaluations. They are continuously evaluated as data is received from Assets, and can also be re-evaluated on historical data when a specific Run is selected.'],
['Annotations and notifications', 'Rules can generate Annotations or send notifications when their logical condition evaluates to true.'],
['Versioning', 'Each Rule is versioned, allowing teams to track changes over time, maintain historical accuracy, and iterate safely as monitoring requirements evolve.'],
['Webhooks', 'Rules can have a webhook connected to send real-time notifications to external systems when a Rule is violated or resolved during live data ingestion. See [Webhook settings](/documentation/reference/manage/webhooks-settings).'],
]}
/>

## Rules vs Ad Hoc Rules

All Rules in Sift share the same CEL-based evaluation engine and structure. However, the way a Rule is created determines how it is managed, who can see it, and how it integrates into your workflow.

### Comparison

The following table compares Rules and Ad Hoc Rules:

<MintTable
  columns={['', 'Rules', 'Ad Hoc Rules']}
  rows={[
['Visible in the Rules tab', 'Yes', 'No'],
['Created via', 'Sift UI or API', 'API only ([BatchUpdateRules](/api-reference/ruleservice/batchupdaterules) with `isExternal: true`)'],
['Editable after creation', 'Yes; each edit creates a new version', 'No; immutable after creation'],
['Versioned', 'Yes; full version history', 'No'],
['Can be added to Report Templates', 'Yes', 'No'],
['Generates Annotations', 'Yes', 'Yes; accessible in the Annotations tab and Explore'],
['Appears in Reports', 'Yes', 'Yes; the Rule logic is displayed in the Report'],
['Typical use case', 'Ongoing telemetry monitoring; standardized review workflows', 'Ad hoc or programmatic evaluation; CI/CD pipelines'],
]}
/>

### When to use Rules

Use Rules for detection logic that:

* Will be reused across multiple Runs.
* Needs to be updated, versioned, or reviewed over time.
* Should be visible and managed by your team in the Sift UI.
* Will be included in a Report Template for standardized reviews.

Rules are the right default for most use cases.

### When to use Ad Hoc Rules

Use Ad Hoc Rules when:

* You are running automated evaluations in a CI/CD pipeline.
* The Rule is ad hoc and does not need to persist in the UI.
* You are generating Annotations programmatically via the API.

#### API reference

The following table lists the API endpoints for working with Ad Hoc Rules. The API uses the term `isExternal` for legacy reasons. This maps to Ad Hoc Rules in the Sift UI.

<MintTable
  columns={['Action', 'Endpoint']}
  columnWidths={['40%', '60%']}
  rows={[
['Create an Ad Hoc Rule', '[BatchUpdateRules](/api-reference/ruleservice/batchupdaterules) with `isExternal: true`'],
['Evaluate Rules and create persistent Annotations', '[EvaluateRules](/api-reference/ruleevaluationservice/evaluaterules)'],
['Preview evaluation without saving', '[EvaluateRulesPreview](/api-reference/ruleevaluationservice/evaluaterulespreview)'],
]}
/>

## Live Rules

Live Rules evaluate expressions against telemetry data as it streams into Sift in real time. They are continuously evaluated as data is received from Assets and can also be re-evaluated on historical data when a specific Run is selected.

### Live Rules and Reports

Live Rules that do not generate any Annotations during the original evaluation are not linked to the Report and will not be included if the Report is rerun. To evaluate a Live Rule that was missed, generate a new Report on the Run and select the Rule manually.
