# Search Framework Integrity Audit

> Runs the PeopleTools SYSAUDIT Search Framework checks (PTSF-1 through PTSF-12) against search definitions, search categories, and search attributes.

---

Site [llms.txt](/llms.txt)

---

<div class="alert alert-primary border shadow-sm mb-4 psl-prospect-cta" role="note">
  <div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3">
    <div>
      <strong>New to psLens?</strong> This page documents one specific report. To see how it runs in the product, what
      the output looks like, and how teams use it in practice, start with a live walkthrough.
    </div>
    <div class="d-flex flex-wrap gap-2">
      <a class="btn btn-sm btn-primary" href="/contact/">Book a Demo</a>
      <a class="btn btn-sm btn-outline-primary" href="/docs/reports/">Browse Reports</a>
    </div>
  </div>
</div><div id="pslens-context-panel" class="card border-info mb-4 d-none">
  <div class="card-header bg-light text-info py-2 fw-bold d-flex align-items-center border-bottom border-info-subtle">
    <i class="bi bi-info-circle-fill me-2"></i>
    <span>Tailored Operational Context</span>
  </div>
  <div class="card-body p-0">
    <ul class="list-group list-group-flush">
      <li id="row-db" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Target Database:</strong>
        <span id="ctx-db" class="badge bg-secondary font-monospace">&mdash;</span>
      </li>
      <li id="row-type" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Context Type:</strong>
        <span id="ctx-type" class="badge bg-light text-dark border font-monospace text-uppercase">&mdash;</span>
      </li>
      <li id="row-severity" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Alert Severity:</strong>
        <span id="ctx-severity" class="badge">&mdash;</span>
      </li>
      <li id="row-time" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Triggered Time:</strong>
        <span id="ctx-time" class="text-muted small">&mdash;</span>
      </li>
      <li id="row-details" class="list-group-item py-2 d-none">
        <strong id="label-details" class="d-block mb-1">Firing Context:</strong>
        <code id="ctx-details" class="d-block p-2 bg-light border rounded small"
          style="white-space: pre-wrap; word-break: break-all;">&mdash;</code>
      </li>
      <li id="row-sql" class="list-group-item py-2 d-none">
        <div class="d-flex justify-content-between align-items-center mb-1">
          <strong id="label-sql">Contextual SQL:</strong>
          <button id="btn-copy-sql" type="button" class="btn btn-sm btn-outline-secondary py-0 px-2 small">
            <i class="bi bi-clipboard me-1"></i>Copy SQL
          </button>
        </div>
        <pre class="mb-0 p-2 bg-light border rounded small"><code id="ctx-sql" style="white-space: pre-wrap; word-break: break-word;">&mdash;</code></pre>
      </li>
    </ul>
  </div>
</div>

<script>
  (function () {
    const params = new URLSearchParams(window.location.search);
    const metadata = params.get('metadata');
    if (!metadata) return;

    try {
      
      const base64 = metadata.replace(/-/g, '+').replace(/_/g, '/');
      const jsonStr = decodeURIComponent(escape(window.atob(base64)));
      const data = JSON.parse(jsonStr);

      if (data) {
        let hasData = false;

        if (data.db) {
          document.getElementById('ctx-db').textContent = data.db;
          document.getElementById('row-db').classList.remove('d-none');
          hasData = true;
        }

        if (data.type) {
          document.getElementById('ctx-type').textContent = data.type;
          document.getElementById('row-type').classList.remove('d-none');
          hasData = true;
        }

        if (data.severity) {
          const severityBadge = document.getElementById('ctx-severity');
          const severity = data.severity.toLowerCase();
          severityBadge.textContent = severity.toUpperCase();
          if (severity === 'critical') {
            severityBadge.className = 'badge bg-danger';
          } else if (severity === 'warning') {
            severityBadge.className = 'badge bg-warning text-dark';
          } else {
            severityBadge.className = 'badge bg-info';
          }
          document.getElementById('row-severity').classList.remove('d-none');
          hasData = true;
        }

        if (data.t) {
          const date = new Date(data.t * 1000);
          document.getElementById('ctx-time').textContent = date.toLocaleString();
          document.getElementById('row-time').classList.remove('d-none');
          hasData = true;
        }

        if (data.details) {
          document.getElementById('ctx-details').textContent = data.details;

          
          const labelDetails = document.getElementById('label-details');
          if (data.type === 'object') {
            labelDetails.textContent = 'Object Metadata Details:';
          } else if (data.type === 'report') {
            labelDetails.textContent = 'Report Description:';
          } else {
            labelDetails.textContent = 'Firing Context:';
          }

          document.getElementById('row-details').classList.remove('d-none');
          hasData = true;
        }

        if (data.sql) {
          document.getElementById('ctx-sql').textContent = data.sql;
          document.getElementById('row-sql').classList.remove('d-none');
          const copyBtn = document.getElementById('btn-copy-sql');
          if (copyBtn && navigator.clipboard) {
            copyBtn.addEventListener('click', function () {
              navigator.clipboard.writeText(data.sql).then(function () {
                copyBtn.innerHTML = '<i class="bi bi-check2 me-1"></i>Copied';
                setTimeout(function () {
                  copyBtn.innerHTML = '<i class="bi bi-clipboard me-1"></i>Copy SQL';
                }, 2000);
              });
            });
          }
          hasData = true;
        }

        if (hasData) {
          document.getElementById('pslens-context-panel').classList.remove('d-none');
        }
      }
    } catch (e) {
      console.error('Failed to parse operational context metadata:', e);
    }
  })();
</script>

## Search Framework Integrity Audit Report

**Report ID:** `ptsf-integrity-audit`
**Category:** Objects

## Purpose

This report finds Search Framework definitions (`PSPTSF_SD`, `PSPTSF_SRCCAT`, `PSPTSF_ATTRS`) that reference an object that does not exist. It runs the twelve checks of the SYSAUDIT search audit (`syssrch.sqc`, `PTSF-1` through `PTSF-12`) and three psLens checks, on demand and without a SYSAUDIT run.

A broken reference usually comes from a project migration that copied a search definition without its query, Application Engine program, or application class. The index build for that definition then fails on the target database.

## Checks

|  Check  |                        Finding                        | Severity |                       Compares                       |
| ------- | ----------------------------------------------------- | -------- | ---------------------------------------------------- |
| PTSF-1  | Category member is not a search definition            | warning  | `PSPTSF_SRCCATAT` → `PSPTSF_SD`                      |
| PTSF-2  | Search definition has no same-named category          | info     | `PSPTSF_SD` → `PSPTSF_SRCCAT`                        |
| PTSF-3  | Search definition is not in its same-named category   | info     | `PSPTSF_SD` → `PSPTSF_SRCCATAT`                      |
| PTSF-4  | Source query does not exist                           | warning  | `PSPTSF_SD` → `PSQRYDEFN`                            |
| PTSF-4  | Source connected query does not exist                 | warning  | `PSPTSF_SD` → `PSCONQRSDEFN`                         |
| PTSF-5  | Delete query does not exist                           | warning  | `PSPTSF_SD` → `PSQRYDEFN`                            |
| PTSF-6  | Pre- or post-processing App Engine does not exist     | warning  | `PSPTSF_SD` → `PSAEAPPLDEFN`                         |
| PTSF-7  | Document security app class does not exist            | warning  | `PSPTSF_SD_DCACL` → `PSPCMPROG`                      |
| PTSF-8  | Indexed field is not in its query                     | warning  | `PSPTSF_SD_ATTR` → `PSQRYFIELD`                      |
| PTSF-9  | Search attribute is not in the attribute catalog      | warning  | `PSPTSF_SD_ATTR` → `PSPTSF_ATTRS`                    |
| PTSF-10 | Search attribute label does not exist                 | info     | `PSPTSF_ATTRS` → `PSDBFLDLABL`                       |
| PTSF-11 | Search attribute is not used by any search definition | info     | `PSPTSF_ATTRS` → `PSPTSF_SD_ATTR`, `PSPTSF_SRCHATTR` |
| PTSF-12 | Insights dashboard role does not exist                | warning  | `PTSF_KIB_ROLE` → `PSROLEDEFN`                       |
| LENS-1  | Data partitioning App Engine does not exist           | warning  | `PSPTSF_SD` → `PSAEAPPLDEFN`                         |
| LENS-2  | Real-Time Indexing trigger record does not exist      | warning  | `PTSF_INDEXTRREC` → `PSRECDEFN`                      |
| LENS-3  | Insights dashboard creator role does not exist        | warning  | `PTSF_KIBCR_ROLE` → `PSROLEDEFN`                     |

`PTSF-4` appears twice because a search definition's source is either a PS Query (`PTSF_SOURCE_TYPE = 'Q'`) or a connected query (`'C'`), and each is looked up in its own table. `LENS-n` checks cover fields and tables that SYSAUDIT does not audit.

## Skipped Checks

A check runs only when every table it compares was read in full. When a table is not whitelisted or its query fails, the check is marked **Skipped** in the summary with the table name and the reason. It is never reported as clean, and it never reports findings from partial data.

Three tables are optional and not in the common whitelist block:

- `PSCONQRSDEFN`. Without it, `PTSF-4` skips connected-query search definitions.
- `PTSF_KIB_ROLE`. Without it, `PTSF-12` is skipped.
- `PTSF_KIBCR_ROLE`. Without it, `LENS-3` is skipped.

Add them with the Search Framework block in [Whitelist Tables](/docs/getting-started/whitelist-tables/).

## Report Output

- **Header** with the database, the generation timestamp, and the number of search definitions, categories, and attributes read.
- **Warning block** naming any table whose query failed.
- **Summary table** with one row per check: `Clean`, the number found, or `Skipped` with the reason.
- **One section per check that has findings**, with a table of the definition (linked to its psLens page) and the missing object.

## Parameters

This report has no configurable parameters.

## Change Tracking

The report is diffable. Each finding is keyed by check, definition, and missing object (for example `PTSF-5|PTPORTALREGISTRY|PTPORTALNOTVALID`), so a scheduled run with notify mode **On change** reports only references that broke or were fixed since the previous run.

When a check cannot run because a query failed, the run records a "check could not run" finding. Without it, the findings of that check from the previous run would read as fixed.

## Interpreting Results

- **`PTSF-4`, `PTSF-5`, `PTSF-6`, `PTSF-7`, `LENS-1`.** The search definition names an object that is not in this database. Migrate the missing object or correct the search definition in Search Designer.
- **`PTSF-7`** looks for the application class's PeopleCode in `PSPCMPROG`. A class that exists but was never saved with code is reported as missing.
- **`PTSF-8`** checks fields flagged to be indexed (`PTSF_ISFIELDTOIDX = 'Y'`). A finding means the field was removed from the query after the search definition was mapped.
- **`PTSF-2` and `PTSF-3`** are informational. PeopleSoft creates a same-named category when a search definition is saved, so a missing one points to an incomplete migration.
- **`PTSF-11`** is cleanup. Delivered PeopleSoft databases carry unused attributes: the PeopleTools 8.62 Campus Solutions demo database reports six.

## Differences from SYSAUDIT

- `PTSF-7` reads `PSPCMPROG`. SYSAUDIT reads `PSAPPCLASSDEFN`.
- `PTSF-11` also counts attributes used by record-based search definitions (`PSPTSF_SRCHATTR`) as used.
- The checks were written from the descriptions of the PeopleTools 8.61 SYSAUDIT checks, not from the SQL in `syssrch.sqc`. Compare against a SYSAUDIT run if you need an exact match.
