# Tracing Every Place a PeopleSoft Field Is Updated Using an AI Agent and the psLens MCP Server

> A real-world walkthrough of how an AI coding agent used the psLens Model Context Protocol (MCP) server to trace every PeopleCode program, App Package, App Engine, record default, and COBOL SQL statement that sets STDNT_CAR_TERM.TUIT_CALC_REQ to 'Y'.

---

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

---

Every PeopleSoft developer and functional analyst has been asked a version of this question:

> *"Where in the system does `STDNT_CAR_TERM.TUITION_CALC_REQ` get set to `'Y'`?"*

On a traditional codebase stored in Git, `ripgrep` answers that question in two seconds. In PeopleSoft, a single flag can be flipped across seven different architectural layers:

- **Record Field Defaults** stored in `PSRECFIELD` (`DEFFIELDNAME` / `DEFRECNAME`)
- **Record Field PeopleCode** (`RowInit`, `FieldChange`, `SavePreChange`, `SavePostChange`)
- **Scroll Buffer Functions** (`UpdateValue()` targeting the record or a view over the record)
- **Database Views** (`STDNT_CAR_TE_VW`) that write through to the underlying table
- **Inline `SQLExec` Statements** with hardcoded `UPDATE PS_STDNT_CAR_TERM` strings
- **Application Packages & Application Engine Programs** (both PeopleCode and SQL actions)
- **COBOL Stored SQL Statements** in `PS_SQLSTMT_TBL`
- **Online Pages and Component Interfaces** where users or integrations toggle the field directly

Worse, raw text searches in Application Designer or against `PSPCMTXT` routinely return false positives from commented-out code (`/* ... */` or `REM`), miss updates that happen through `SQLExec("UPDATE PS_STDNT_CAR_TERM ...")`, and ignore COBOL stored SQL entirely.

Here is what happens when you hand that exact prompt to an AI coding agent connected to a PeopleSoft Campus Solutions 9.2 environment through the **psLens MCP Server**.

---

## The Prompt

The entire investigation started with a single one-line prompt in the IDE:

```text
Use pslens to find all areas where STDNT_CAR_TERM.TUITION_CALC_REQ gets set to "Y".
```

Notice the subtle trap in the prompt itself: the human asked for `TUITION_CALC_REQ` (after the page label *"Tuition Calc Required"*), whereas the actual PeopleSoft column name on `STDNT_CAR_TERM` is truncated to 13 characters: **`TUIT_CALC_REQ`**.

Here is the exact sequence of MCP tool calls the agent executed to resolve the schema, trace every dependency layer, verify the source code, and eliminate false positives.

```mermaid
flowchart TD
    P["User Prompt:\nFind where STDNT_CAR_TERM.TUITION_CALC_REQ = 'Y'"]
    T1["list_databases\nIdentify connected CS 9.2 instance"]
    T2["get_record_schema\nInspect STDNT_CAR_TERM fields & defaults"]
    T3["analyze_record_field_impact\nBlast radius across pages, CIs, views & PeopleCode"]
    T4["search_code_sql\nSearch PeopleCode, App Engine SQL & SQL Objects"]
    T5["query_table (SQLSTMT_TBL)\nInspect COBOL stored UPDATE/INSERT statements"]
    T6["get_peoplecode\nRead full event source & filter commented-out code"]
    R["Complete Cross-Layer Impact Report"]

    P --> T1 --> T2 --> T3 --> T4 --> T5 --> T6 --> R
```

---

## Step-by-Step: How the Agent Used the psLens MCP Tools

### Discovering the Environment and Resolving the Field Name

First, the agent called `list_databases` to see which PeopleSoft environments were connected, followed by `get_record_schema` on `STDNT_CAR_TERM`:

```json
{
  "tool": "get_record_schema",
  "arguments": {
    "db": "CS92DEV-CHG",
    "record_name": "STDNT_CAR_TERM"
  }
}
```

From `get_record_schema`, the agent immediately spotted two critical facts without human intervention:

- The column on `STDNT_CAR_TERM` is **`TUIT_CALC_REQ`** (not `TUITION_CALC_REQ`).
- In `PSRECFIELD`, `STDNT_CAR_TERM.TUIT_CALC_REQ` has a **Record Field Default** pointing to **`BUS_UNIT_TBL_SF.TERM_ACT_INIT_SET`** (*Term Activation Initial Setting*). Whenever `TERM_ACT_INIT_SET = 'Y'` on the Student Financials Business Unit, any new `STDNT_CAR_TERM` row or `SetDefault(STDNT_CAR_TERM.TUIT_CALC_REQ)` call automatically initializes `TUIT_CALC_REQ` to `'Y'`.

### Running a Record.Field Blast Radius Analysis

Next, the agent called `analyze_record_field_impact` to map every PeopleSoft object that references `STDNT_CAR_TERM.TUIT_CALC_REQ`:

```json
{
  "tool": "analyze_record_field_impact",
  "arguments": {
    "db": "CS92DEV-CHG",
    "record_name": "STDNT_CAR_TERM",
    "field_name": "TUIT_CALC_REQ"
  }
}
```

In a single round trip, `analyze_record_field_impact` returned:

- **Online Pages** displaying `STDNT_CAR_TERM.TUIT_CALC_REQ` (`STDNT_TUITION_CALC`, `WITHDRAWAL`, `QUICK_ADMIT_WRK2`, `OEE_ENRL_DATES`) and whether each page field is editable or display-only.
- **Component Interfaces** exposing the field and their access mode (`ENRL_GRADES` and `ENRL_TERM_ACTIVTN` as Read/Write; `CHI_STDNT_ACTIVATION` as Read-Only).
- **Direct PeopleCode References** to `STDNT_CAR_TERM.TUIT_CALC_REQ`.

### Searching PeopleCode, App Engine SQL, and SQL Objects

Direct `RECORD.FIELD` metadata references only tell part of the story. In Campus Solutions, many programs update `PS_STDNT_CAR_TERM` via raw `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' ...")`, through the view `STDNT_CAR_TE_VW`, or via Application Engine SQL actions.

The agent invoked `search_code_sql` across `peoplecode`, `appengine`, and `sqlobject` scopes:

```json
{
  "tool": "search_code_sql",
  "arguments": {
    "db": "CS92DEV-CHG",
    "query": "TUIT_CALC_REQ",
    "scope": "all"
  }
}
```

This surfaced 89 PeopleCode matches and all Application Engine SQL steps referencing `TUIT_CALC_REQ`, including batch SQL updates in the Student Group process (`SCC_STD_GRP` steps `MVOVRD.MVOVRD45` and `MVTRGT.MVTRGT45`) and `SQLExec` updates buried inside Residency (`RESIDENCY_OFF`), Optional Fees (`OPT_FEE_STDNT`), Student Equation Variables (`STDNT_EQUTN_VAR`), Student Groups (`STDNT_GRPS`), and Student Waivers (`SSF_STD_WAIVER` / `SSF_SWVR_PS`).

### Checking COBOL Stored SQL in `SQLSTMT_TBL`

Campus Solutions relies heavily on COBOL batch programs (`SRPCENRL`, `SRPCCUMM`, `SFPBCALC`, `SFPRCALC`) whose SQL statements live in `PS_SQLSTMT_TBL`, completely outside App Designer's PeopleCode and SQL definitions.

Using `query_table`, the agent queried `SQLSTMT_TBL` for statements touching `TUIT_CALC_REQ` or updating `PS_STDNT_CAR_TERM`:

```json
{
  "tool": "query_table",
  "arguments": {
    "db": "CS92DEV-CHG",
    "record_name": "SQLSTMT_TBL",
    "fields": ["PGM_NAME", "STMT_TYPE", "STMT_NAME", "STMT_TEXT"],
    "filters": [
      { "field": "STMT_TEXT", "op": "like", "value": "%TUIT_CALC_REQ%" }
    ],
    "limit": 100
  }
}
```

That query separated the COBOL programs that **clear** the flag (`SFPBCALC`, `SFPBHECS`, `SFPCCALC`, `SFPCLCAN`, `SFPRCALC`, `SFPREFND`, which all run `SET TUIT_CALC_REQ = 'N'`) from the COBOL program that **updates** it during enrollment and term statistics processing: **`SRPCCUMM`** (`STMT_NAME = 'CUMTERM'`), which executes:

```sql
UPDATE PS_STDNT_CAR_TERM
   SET ...,
       FA_STATS_CALC_REQ = 'Y',
       TUIT_CALC_REQ = :54,
       ...
 WHERE EMPLID = :63
   AND ACAD_CAREER = :64
   AND INSTITUTION = :65
   AND STRM = :66
```

### Reading Full Event Context and Catching Commented-Out Code

A substring search for `TUIT_CALC_REQ` matched `STDNT_CAR_SEQ.STDNT_CAR_NBR.SavePreChange` at line 156:

```peoplecode
UpdateValue(STDNT_CAR_TE_VW.TUIT_CALC_REQ, &I, "Y");
```

If you only look at a single matching line from a grep or SQL query on `PSPCMTXT`, you would list `STDNT_CAR_SEQ.STDNT_CAR_NBR.SavePreChange` as an active trigger. Instead, the agent called `get_peoplecode` to inspect the surrounding program:

```json
{
  "tool": "get_peoplecode",
  "arguments": {
    "db": "CS92DEV-CHG",
    "name": "STDNT_CAR_SEQ.STDNT_CAR_NBR",
    "event_or_step": "SavePreChange"
  }
}
```

Inspecting the full PeopleCode event revealed that Oracle had wrapped lines 150–159 of `STDNT_CAR_SEQ.STDNT_CAR_NBR.SavePreChange` inside a `/* ... */` block comment and moved the active logic to `DERIVED_CS.ROW_NUMBER.SavePreChange`! The agent also used `get_peoplecode` on `STDNT_CAR_TERM.REG_CARD_DATE.SavePreChange` to extract the exact list of 20 `FieldChanged(...)` conditions that trigger tuition recalculation when a student's term record is edited.

---

## The Complete Research Output

Within seconds, the agent produced a complete inventory of every mechanism that sets `STDNT_CAR_TERM.TUIT_CALC_REQ` to `'Y'`:

### Record Field Default (`PSRECFIELD`)

- **`STDNT_CAR_TERM.TUIT_CALC_REQ`** (and `STDNTCARTERM_DR.TUIT_CALC_REQ`) defaults from **`BUS_UNIT_TBL_SF.TERM_ACT_INIT_SET`**. When `TERM_ACT_INIT_SET = 'Y'`, any new row or `SetDefault(STDNT_CAR_TERM.TUIT_CALC_REQ)` call (such as in `STDNT_CAR_TERM.STRM.FieldChange` line 41 and `STDNT_CAR_TERM.STDNT_CAR_NBR.FieldChange` line 40) sets `TUIT_CALC_REQ` to `'Y'`.

### Record Field PeopleCode

- **`STDNT_CAR_TERM.EMPLID.RowInit`** (line 362): Sets `STDNT_CAR_TERM.TUIT_CALC_REQ = "Y"` when `%Mode = "A"` (Add mode).
- **`STDNT_CAR_TERM.REG_CARD_DATE.SavePreChange`** (line 33): In `TERM_HISTORY` or `STDNT_ACTIVATION`, sets `STDNT_CAR_TERM.TUIT_CALC_REQ = "Y"` on an existing term row if any of these 20 fields on `STDNT_CAR_TERM` changed: `ACADEMIC_LOAD`, `ACAD_LEVEL_BOT`, `ACAD_LEVEL_PROJ`, `ACAD_LOAD_APPR`, `STDNT_CAR_NBR`, `ACAD_PROG_PRIMARY`, `ACAD_YEAR`, `BILLING_CAREER`, `CUR_RESIDENT_TERMS`, `TRF_RESIDENT_TERMS`, `ELIG_TO_ENROLL`, `FORM_OF_STUDY`, `FULLY_ENRL_DT`, `LAST_DATE_ATTENDED`, `PROJ_BILL_UNT`, `OVRD_BILL_UNITS`, `WITHDRAW_CODE`, `WITHDRAW_DATE`, `INSTITUTION`, or `STRM`.
- **`STDNT_CAR_TERM.OVRD_TUIT_GROUP.FieldChange`** (line 3): In `STDNT_ACTIVATION` or `TUITION_CALC`, sets `STDNT_CAR_TERM.TUIT_CALC_REQ = "Y"` when `OVRD_TUIT_GROUP` changes.
- **`DERIVED_CS.ROW_NUMBER.SavePreChange`** (line 26): In `ACAD_PLAN` (Student Program/Plan), sets `STDNT_CAR_TE_VW.TUIT_CALC_REQ = "Y"` for terms `>= &TERM` when `ACAD_PROG.PROG_STATUS` is `"AC"`, `"AD"`, or `"AP"`.
- **`DERIVED_CS.UPDATE_CUM.SavePreChange`** (line 23) & **`FUNCLIB_CS.TOT_CUMULATIVE.FieldFormula`** (`get_cur_term`, line 426): Set `STDNT_CAR_TERM.TUIT_CALC_REQ` to `"Y"` via `UpdateValue(STDNT_CAREER.EMPLID, ..., STDNT_CAR_TERM.TUIT_CALC_REQ, 1, "Y")` when updating term/cumulative enrollment statistics.
- **`STDNT_ENRL_OEE.END_DT.FieldChange`** (lines 2–3): In `OEE_CALENDAR`, sets `GetLevel0()(1).STDNT_CAR_TERM(1).STDNT_CAR_TERM.TUIT_CALC_REQ.Value = "Y"`.
- **`SSR_HECS_ELEC.SSR_HECS_XMPT_STAT.SavePreChange`** (line 3): Sets `STDNT_CAR_TERM.TUIT_CALC_REQ = "Y"` when `FieldChanged(SSR_HECS_ELEC.SSR_HECS_XMPT_STAT)`.
- **`RESIDENCY_OFF.INSTITUTION.SavePostChange`** (lines 14, 16, 21): In `RESIDENCY_ADMA`, `RESIDENCY_ADMP`, or `RESIDENCY_PERS`, when `DERIVED_CC.TUIT_CALC_REQ = "Y"` (set by `FieldChange` on `RESIDENCY_OFF.EFFECTIVE_TERM`, `INSTITUTION`, `RESIDENCY`, or `TUITION_RES`) or when a residency row is deleted, runs `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' WHERE EMPLID = :1 AND ACAD_CAREER = :2 AND INSTITUTION = :3 AND STRM >= :4", ...)`.
- **`OPT_FEE_STDNT.VALID_VALUE.SavePreChange`** (line 5): When `RecordChanged(OPT_FEE_STDNT.VALID_VALUE)`, runs `SQLExec("update ps_stdnt_car_term set tuit_calc_req = 'Y' where emplid = :1 and institution = :2 and strm = :3 and billing_career = :4", ...)`.
- **`STDNT_EQUTN_VAR.STRM.SavePostChange`** (lines 7, 10): Runs `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' WHERE EMPLID = :1 AND BILLING_CAREER = :2 AND INSTITUTION = :3 AND STRM = :4", ...)` for both `STDNT_EQUTN_VAR.STRM` and `&PRIOR_TERM`.
- **`STDNT_GRPS.INSTITUTION.SavePostChange`** (lines 32, 49, 71): In `STDNT_GROUPS_ADMA`, `STDNT_GROUPS_ADMP`, or `STDNT_GROUPS_PERS`, when `&TUIT_RECALC_REQ = "Y"` (or a student group row is deleted), runs `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' WHERE EMPLID = :1 AND INSTITUTION = :2 AND ACAD_CAREER = :3 AND STRM = :4", ...)` for terms with `TERM_BEGIN_DT >= &STDNT_GROUP_EFFDT`.

### Application Package PeopleCode

- **`SSR_CAREER_TERM:Accessors:StudentTerm`** (`OnExecute`):
  - Lines 1284 / 4268: Sets `%This.TUIT_CALC_REQ = &recBUS_UNIT_TBL_SF.TERM_ACT_INIT_SET.Value` during term creation.
  - Lines 1785 / 4769: Sets `%This.TUIT_CALC_REQ = "Y"` using the same field-change rules as `STDNT_CAR_TERM.REG_CARD_DATE.SavePreChange`.
  - Lines 2629 / 5613: Property setter `set TUIT_CALC_REQ` assigns `%This.data.TUIT_CALC_REQ.Value = &NewValue`.
- **`SSR_FA_DATA_UPD:ProcessFAUnits`** (`OnExecute`, line 244): Sets `&REC_stdntcarTerm.TUIT_CALC_REQ.Value = "Y"` and updates `STDNT_CAR_TERM`.
- **`SCC_POP_UPDATE:APEngine:STDNT_EQUTN_VAR`** (line 63), **`SCC_POP_UPDATE:APEngine:PopUpdateCommonProcessing`** (lines 734, 1234), and **`SCC_POP_UPDATE:APEngine:RowByRowProcessing`** (lines 734, 1234):
  - When Population Update inserts or updates `STDNT_EQUTN_VAR`, it loads `STDNT_CAR_TERM` by key, sets `&StudentCarTerm.TUIT_CALC_REQ.Value = "Y"`, and calls `.Update()`.
  - When Population Update modifies `STDNT_CAR_TERM.OVRD_TUIT_GROUP`, it also sets `TUIT_CALC_REQ` to `"Y"`.
- **`SSF_TUITION_AND_FEES:Components:SSF_STD_WAIVER`** (`OnExecute`, line 414): Runs `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' WHERE EMPLID = :1 AND INSTITUTION = :2 AND STRM = :3", SSF_STDNT_WVR.EMPLID, &Institution, SSF_STDNT_WVR.STRM)` when saving student waivers.

### Application Engine Programs

- **`SSF_SWVR_PS`** (`LINEPROC.OnExecute` PeopleCode, line 11): Runs `SQLExec("UPDATE PS_STDNT_CAR_TERM SET TUIT_CALC_REQ = 'Y' WHERE EMPLID = :1 AND INSTITUTION = :2 AND STRM = :3", SSF_SWVR_PS_AET.EMPLID, &Institution, SSF_SWVR_PS_AET.STRM)`.
- **`SCC_STD_GRP`** (`MVOVRD.MVOVRD45` and `MVTRGT.MVTRGT45` SQL actions): Updates `PS_STDNT_CAR_TERM A SET A.TUIT_CALC_REQ = 'Y'` where `STDNT_GRP_AET.TUIT_CALC_REQ_LBL = 'Y'`, `A.TUIT_CALC_REQ = 'N'`, and `TERM_BEGIN_DT >= ASOFDATE`.

### COBOL Stored SQL (`SQLSTMT_TBL`)

- **`SRPCCUMM` (`CUMTERM`)**: Updates `PS_STDNT_CAR_TERM SET ..., FA_STATS_CALC_REQ = 'Y', TUIT_CALC_REQ = :54, ...` during enrollment and cumulative statistics processing (`SRPCENRL` / `SRPCENVB`).

### Online Pages & Component Interfaces

- **Online Pages**: `STDNT_CAR_TERM.TUIT_CALC_REQ` is directly editable on `STDNT_TUITION_CALC` (components `STDNT_ACTIVATION` and `TUITION_CALC`), `WITHDRAWAL` (component `TERM_HISTORY`), and `QUICK_ADMIT_WRK2` (component `QUICK_ADMIT`).
- **Component Interfaces**: Exposed as Read/Write (`BCACCESS = 2`) on `ENRL_GRADES` and `ENRL_TERM_ACTIVTN`.

---

## Why This Changes PeopleSoft Impact Analysis

Answering this question manually in Application Designer typically takes an experienced PeopleSoft developer an hour or more of cross-referencing `Find Definition References`, `Find In` across PeopleCode and SQL, checking record field properties, and writing custom SQL against `PS_SQLSTMT_TBL` — assuming they even remember to check views like `STDNT_CAR_TE_VW` and COBOL stored statements.

With the **psLens MCP Server**, an AI agent in Google Antigravity, Claude Code, Cursor, or VS Code has read-only, whitelisted access to the entire PeopleSoft metadata graph and code repository:

|           MCP Tool Used           |                                                        Role in the Investigation                                                        |
| :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| **`list_databases`**              | Identified the target Campus Solutions 9.2 database (`CS92DEV-CHG`)                                                                     |
| **`get_record_schema`**           | Resolved `TUITION_CALC_REQ` → `TUIT_CALC_REQ` and uncovered the `BUS_UNIT_TBL_SF.TERM_ACT_INIT_SET` default                             |
| **`analyze_record_field_impact`** | Mapped every page, component, Component Interface, and direct PeopleCode reference                                                      |
| **`search_code_sql`**             | Found inline `SQLExec("UPDATE PS_STDNT_CAR_TERM ...")` calls, view updates (`STDNT_CAR_TE_VW`), and App Engine SQL actions              |
| **`query_table`**                 | Inspected COBOL stored SQL statements in `PS_SQLSTMT_TBL` (`SRPCCUMM` vs. `SFPBCALC`)                                                   |
| **`get_peoplecode`**              | Verified full event logic, extracted the 20 trigger fields on `REG_CARD_DATE.SavePreChange`, and filtered out commented-out legacy code |

---

## Try It on Your Own Environment

The embedded MCP server ships inside psLens with zero extra infrastructure required:

- Read the [MCP Server & Agent Skills Documentation](/docs/mcp/) for setup instructions across Google Antigravity, Claude Code, VS Code, and Claude Desktop.
- Explore the [Connecting AI Agents to PeopleSoft with MCP](/blog/connecting-ai-agents-to-peoplesoft-with-mcp/) architecture overview.
- [Book a live demo](/contact/) to see psLens MCP answer impact analysis and security questions against a live PeopleSoft environment.

---

### Subscribe for Updates

If you found this article helpful, subscribe to get notified of new PeopleSoft technical notes and psLens updates.











<div class="card bg-light border-0 border-start border-primary border-3 my-4 shadow-sm">
    <div class="card-body p-3">
        <form action="https://subscribe.cedarhillsgroup.com/subscribe" method="POST" class="row align-items-center g-3">
            
            <div style="position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none;" aria-hidden="true">
                <label for="hp-pslens-horiz-website">Website</label>
                <input type="text" id="hp-pslens-horiz-website" name="website" tabindex="-1" autocomplete="off">
            </div>
            <input type="hidden" name="redirect_to" value="https://pslens.com/subscribed/">
            <input type="hidden" name="topic_id" value="pslens">

            <div class="col-lg-5">
                <h6 class="mb-1 text-dark fw-bold">Stay Updated</h6>
                <p class="mb-0 text-muted small" style="font-size: 0.85rem;">Receive new articles directly in your inbox.</p>
            </div>

            <div class="col-md-6 col-lg-3">
                <input type="text" name="first_name" placeholder="First Name (optional)"
                    class="form-control form-control-sm">
            </div>

            <div class="col-md-6 col-lg-3">
                <input type="email" name="email" required placeholder="Email Address"
                    class="form-control form-control-sm">
            </div>

            <div class="col-12 col-lg-1">
                <button type="submit" class="btn btn-primary btn-sm w-100">Join</button>
            </div>
        </form>
    </div>
</div>
