> ## 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.

# Overview

> Learn how Rules, Reports, Annotations, and Campaigns work together when reviewing a Run

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>;
};

After completing this topic, you can understand how Rules, Reports, Annotations, and Campaigns work together and know where to start when reviewing a Run.

## How Review works

Review in Sift is organized around a four-object pipeline:

**Rule → Report → Annotation → Campaign**

Each object has a distinct role. Understanding what each one does and why they are separate is the fastest way to orient yourself before starting a review.

## The review pipeline

### Rules

A Rule defines what to look for in your telemetry.

You write a logical condition using the [Common Expression Language (CEL)](https://cel.dev). For example, flagging when a channel exceeds a threshold or a log contains a specific string. Rules are reusable across Runs, versioned over time, and shared across your team.

Rules are the starting point for anyone setting up automated detection.

### Reports

A Report evaluates one or more Rules against a specific Run and collects the results in one place. When you generate a Report, Sift runs each Rule's expression against the Run's telemetry and shows you which Rules passed and which generated issues.

Reports are the primary workspace for reviewing a single Run. Most reviewers spend the majority of their time here.

### Annotations

When a Rule's condition evaluates to true during a Run, Sift creates an Annotation: a timestamped marker linked to the specific channel values that triggered the Rule. Annotations can also be created manually in Explore.

There are two types:

* **Phase Annotations**: informational markers for milestones such as "Engine Ignition" or "Max-Q". They have no status and cannot be assigned.
* **Data Review Annotations**: issue-tracking entries with a status workflow (`Open`, `Failed`, `Accepted`), an assignee field, and a comment thread. Use these when data needs investigation.

Annotations represent the actual work of review: triaging findings, assigning ownership, and tracking resolution.

### Campaigns

A Campaign groups Reports from multiple Runs into a single workspace. Use a Campaign when you need to coordinate a review effort that spans several Runs. For example, all qualification tests for a hardware release.

Campaigns are not required for reviewing a single Run. Start with a Campaign only when you are managing a multi-run effort.

## Who uses each object

<MintTable
  columns={['Object', 'Primary audience', 'When they use it']}
  rows={[
['Rules', 'Test engineers; data engineers', 'When setting up detection logic before or between Runs'],
['Reports', 'Anyone reviewing a Run', 'During and after a Run to assess results'],
['Annotations', 'Reviewers; engineers investigating issues', 'Day-to-day issue triage and resolution'],
['Campaigns', 'Review leads; program managers', 'When coordinating review across many Runs']
]}
/>

## Where to start

<MintTable
  columns={['Your situation', 'Start here']}
  rows={[
['You are new to Review and want to try it end to end', '[Detect and review issues in a Run](/documentation/review/detect-and-review-issues-in-a-run)'],
['You need to create detection logic for your telemetry', '[Detect deviations automatically using Rules](/documentation/review/detect-deviations-automatically-using-rules)'],
['You want to stop picking Rules manually every time', '[Set up a repeatable review checklist](/documentation/review/set-up-a-repeatable-review-checklist)'],
['You are triaging issues from a completed Run', '[Triage and close out flagged issues](/documentation/review/triage-and-close-out-flagged-issues)'],
['You are coordinating review across many Runs', '[Track a multi-run review campaign](/documentation/review/track-a-multi-run-review-campaign)']
]}
/>
