LIBAUDIT/i — Library Retention Sweep

SteelFrame X application operation manual  ·  ← back to Operation Manuals  ·  Sign On

LIBAUDIT/i is a batch library-retention utility: it classifies candidate objects by age against a retention policy, purges those past the threshold, keeps the rest, and persists a verifiable run summary as real object state. It is driven the way IBM i shops actually run batch REXX — a CL program (LADRIVER) fetches the policy from a data area and hands it to STRREXPRC, which runs the REXX/400 exec LAMAIN; that exec does all its work through ADDRESS COMMAND to CL and calls external routines LACLASS, LARPT and LASELFTEST. Honest framing: this application is also a deliberate REXX/400 language-reference exercise. Each moving part is chosen to drive a specific REXX feature end-to-end — the CL→REXX data-area bridge, PARSE/PULL, string abuttal and FORMAT, CHANGESTR/COUNTSTR, CONDITION under CALL ON vs SIGNAL ON, same-source-file and *LIBL external-routine resolution, and the REXX error-28 LEAVE-name boundary. It is documented here as a real retention utility, but it exists primarily to exercise those features. This manual is grounded entirely in the committed source (rexx-app/src/ladriver.cl.txt, lamain.rexx.txt, laclass.rexx.txt, larpt.rexx.txt, laselftest.rexx.txt, and the test/libaudit.mjs driver).

Contents

A. Overview & Architecture ↑ top

A.1 What it does

LIBAUDIT/i performs one retention sweep per run:

With the tested policy of 30 days, the five candidates (ages 5, 40, 12, 91, 30) resolve to purged=2, kept=3: only the ages strictly greater than 30 (40 and 91) are purged; age 30 is the boundary and is kept (the comparison is >, not ).

A.2 The CL→STRREXPRC→REXX bridge

The application is split across two layers with a hard, purposeful boundary:

The benefit is that policy retrieval lives where the OS actually permits it (CL), while the classification, reporting, and control logic stay in REXX, and the result is provable from committed objects rather than transcript scraping. Everything runs in library LIBAUD (with report routine LARPT in LARPTLIB and candidates in LAWORK).

A.3 Component & flow

  CL DRIVER               BRIDGE                 REXX EXECS
  ---------               ------                 ----------
  LADRIVER  ---------->   STRREXPRC  ------->    LAMAIN  (the sweep)
   RTVDTAARA LAPOLICY       PARM='polage mode'     PARSE ARG polage runmode
   CRTLIB LAWORK (if none)                         PULL ovr  (empty in batch)
   PARM = &POLAGE &RUNMODE                          CALL ON ERROR name handler
        |                                          |
        |                                          +--CALL--> LACLASS (same srcfile)
        |                                          |            age vs policy -> PURGE/KEEP
        |                                          +--CALL--> LARPT  (LARPTLIB/QREXSRC, *LIBL)
        |                                          |            CHANGESTR/COUNTSTR redact+total
        |                                          +--CALL--> LASELFTEST (LEAVE-name error 28)
        v                                          |            trapped via SIGNAL ON SYNTAX
  CHKOBJ LARESULT  <---- CHGDTAARA LARESULT <------+ ADDRESS COMMAND: CRTPF/DLTF/CHKOBJ/CRTDTAARA

A single run flows: LADRIVER reads LAPOLICY → calls STRREXPRC with the policy+mode PARM → LAMAIN preflights, creates the five candidates, classifies each via LACLASS, DLTFs the over-threshold ones, formats the audit line via LARPT, persists LARESULT, runs the LASELFTEST boundary check → LADRIVER re-verifies LARESULT exists.

A.4 Object inventory

ObjectTypeRole
LADRIVERCL program (QCLSRC)Batch entry point; policy retrieval + STRREXPRC + state re-check.
LAMAINREXX (QREXSRC)The sweep exec: classify / purge / report / persist.
LACLASSREXX (same QREXSRC)External classification routine (age vs policy).
LARPTREXX (LARPTLIB/QREXSRC)External report routine resolved via *LIBL.
LASELFTESTREXX (same QREXSRC)Deliberate LEAVE-name error-28 self-test.
LAPOLICY*DTAARA (LIBAUD)Retention age in days (input; read by CL).
LARESULT*DTAARA (LIBAUD)Persisted run summary "P=n K=n" (output).
LAWORK*LIBScratch/candidate library (created on demand).
LAOBJ0001-0005*FILE (LAWORK)Five synthetic candidate objects.
QREXSRCSource PFREXX source (LIBAUD and LARPTLIB copies).
QCLSRCSource PFCL source (LADRIVER).

The full catalogue is 1 CL program + 4 REXX execs, 2 data areas, 1 scratch library holding up to 5 candidate files, over 2 source PFs. Sections D and F expand each.

B. Online & Access Model ↑ top

B.1 No screen — LIBAUDIT/i is a batch utility

Honest statement: LIBAUDIT/i has no interactive 5250 screen, no DDS display file, no subfile, and no menu. It is a batch administrative exec. The only "user interface" is the job's SAY transcript in the joblog and the committed object state it leaves behind. There is nothing to key an option against; an operator runs the CL driver and then inspects results. This is normal for a REXX/400 retention utility — it is driven, not operated interactively.

Because there is no screen, everything an operator does is on the command-entry line (or via a JOBQ/scheduler): set the policy, submit the driver, then read the transcript and verify committed state.

B.2 The command/entry line

On IBM i, the utility is reached by calling its CL driver by name. Before invoking it, the job's library list must include LIBAUD and LARPTLIB — the tested job runs with LIBL = QSYS LIBAUD LARPTLIB QGPL QTEMP and CURLIB = LIBAUD. LARPTLIB must be on the list so STRREXPRC can resolve the external routine LARPT along *LIBL.

To do thisType on the command line
Set the retention policy (days)CHGDTAARA DTAARA(LIBAUD/LAPOLICY) VALUE('30')
Run the retention sweep (the driver)CALL LIBAUD/LADRIVER (or SBMJOB it)
Run the REXX exec directly (advanced)STRREXPRC SRCMBR(LAMAIN) SRCFILE(LIBAUD/QREXSRC) PARM('0000030 LIVE')
Inspect the persisted run summaryDSPDTAARA DTAARA(LIBAUD/LARESULT)

The driver takes no CALL parameters — it reads the policy from LAPOLICY itself, so a scheduled submission is a bare CALL LIBAUD/LADRIVER. Running LAMAIN directly is possible but bypasses the CL data-area bridge: you must supply the policy age and mode as a PARM token yourself (e.g. '0000030 LIVE'), because RTVDTAARA ... RTNVAR() only binds inside a structured CL program (see A.2).

B.3 The operator override card

LAMAIN issues a PULL for an operator override card immediately after startup. In batch there are no stacked input lines, so the external PULL resolves to an empty string, and the exec treats that honestly as "no override supplied" — not as an error:

pull ovr
if ovr = '' then
  say 'no operator override card supplied (expected in batch)'
else
  say 'operator override:' ovr

This is the closest thing to interactive input the utility has: if the job were run with a stacked queue line, that value would be surfaced; in normal batch it is empty and the sweep proceeds with the policy from the data area. There is no maker–checker, approval gate, or four-eyes workflow — the sweep applies its purges immediately.

C. Batch Jobs & the Sweep Run ↑ top

LIBAUDIT/i runs as a single batch job: LADRIVER, which invokes the LAMAIN exec through STRREXPRC. There is one logical run — the retention sweep — with three visible phases: candidate classification/purge, the capacity report, and the error-handling/self-test. The policy is read from the LAPOLICY data area, so a scheduled submission needs no parameters.

-- set the retention threshold the next sweep runs against (days)
CHGDTAARA DTAARA(LIBAUD/LAPOLICY) VALUE('30')

-- then submit the (parameterless) driver
SBMJOB CMD(CALL PGM(LIBAUD/LADRIVER)) JOB(LIBAUDIT)

C.1 The retention sweep (LADRIVER → LAMAIN)

ProgramPurposeRoutines calledInputsOutputsFrequency
LADRIVER Batch entry point: fetch policy, ensure LAWORK, run the exec, re-verify result. STRREXPRC(LAMAIN); CL RTVDTAARA/CHKOBJ/CRTLIB. LIBAUD/LAPOLICY data area; run mode 'LIVE'. Committed LAWORK library + LARESULT data area (CHKOBJ'd on return). On demand / scheduled.
LAMAIN Classify five candidates, purge over-threshold, report, persist summary, self-test. LACLASS, LARPT, LASELFTEST; ADDRESS COMMAND CRTPF/DLTF/CHKOBJ/CRTDTAARA/CHGDTAARA. PARM 'polage runmode' (e.g. '0000030 LIVE'). SAY transcript; LAOBJ* files (kept/deleted); LARESULT = "P=n K=n". Per driver run.

The sweep proper (LAMAIN, lines 38–83): it seeds five in-exec candidates with known ages, and for each one CRTPFs a real candidate file in LAWORK, calls class = laclass(oage, polage), and on PURGE issues a real DLTF FILE(LAWORK/onm) guarded by an rc = 0 check, accumulating bytesreclaimed = bytesreclaimed + (oage * 5120) and appending onm:PURGED; to the raw report; otherwise it counts the object as kept and appends onm:KEPT;.

Expected SAY (policy=30, five candidates):
  LIBAUDIT/i sweep starting, policy=30d mode=LIVE
  no operator override card supplied (expected in batch)
  candidates evaluated=5 purged=2 kept=3   ages 40 & 91 over 30

C.2 Classification & the exponential capacity report

Classification (LACLASS) is a pure function: PURGE when oage > pol, else KEEP. The boundary is strict — age exactly equal to the policy is kept (candidate LAOBJ0005, age 30, policy 30, is kept).

After the loop, the reclaimed-byte total is emitted through REXX FORMAT in exponential form: format(bytesreclaimed, , , 3, 0). With bytesreclaimed = 670720 the engine's TRL-2 FORMAT produces scientific notation with a 3-digit exponent field:

reclaimed bytes (exponential): 6.70720E+005

The redacted audit line is then built by the external routine LARPT (see F.3): it runs CHANGESTR('LAOBJ', raw, 'OBJ#') to redact every object-name prefix and COUNTSTR('PURGED;', ...) / COUNTSTR('KEPT;', ...) to total the outcomes, returning one line:

AUDIT REPORT [OBJ#0001:KEPT;OBJ#0002:PURGED;OBJ#0003:KEPT;OBJ#0004:PURGED;OBJ#0005:KEPT;] totals purged=2 kept=3

Finally the run summary is persisted through the CL bridge: CHKOBJ the LARESULT data area, CRTDTAARA it if absent, then CHGDTAARA it to 'P=' || purged || ' K=' || kept — i.e. P=2 K=3.

C.3 Self-test & error handling

Two distinct REXX conditions are exercised and trapped, and both are expected outcomes, not failures:

Expected SAY (the two trapped conditions):
  preflight probe rc=30 handlerfired=1
  handler: cond=ERROR inst=CALL rc=30
  selftest trapped as expected: cond=SYNTAX inst=SIGNAL rc=40
The self-test runs in an isolated external CALL precisely so the REXX error 28 it triggers does not abort the whole sweep. This is the utility deliberately proving it can rely on the engine's LEAVE-name contract (loud error 28 → error 40 to the caller) without crashing.

D. Data Files & Objects ↑ top

LIBAUDIT/i has no relational schema — it touches IBM i objects (data areas, a library, physical files) rather than DB2 tables. The objects it reads, creates, and commits, grounded in the source and the test/libaudit.mjs harness:

LAPOLICY — retention policy data area (input)

AttributeValueMeaning
ObjectLIBAUD/LAPOLICY*DTAARA, TYPE(*CHAR) LEN(6).
Valuee.g. 30Retention threshold in days; an object strictly older is purged.
Read byLADRIVERVia RTVDTAARA ... RTNVAR(&POLAGE) (CL-only bridge).

The zoned 6-char token is de-padded in the exec with polage = polage + 0.

LARESULT — run summary data area (output)

AttributeValueMeaning
ObjectLIBAUD/LARESULT*DTAARA, TYPE(*CHAR) LEN(50).
ValueP=2 K=3Purged / kept counts of the completed sweep.
Written byLAMAINCHKOBJCRTDTAARA (if absent) → CHGDTAARA.
Re-checked byLADRIVERCHKOBJ OBJ(LIBAUD/LARESULT) OBJTYPE(*DTAARA) on return.

This is the utility's authoritative result: it proves the sweep's outcome independently of the SAY transcript.

LAWORK — scratch/candidate library

AttributeValueMeaning
ObjectLAWORK*LIB, created on demand by LADRIVER (CHKOBJ + MONMSG SFF9801 → CRTLIB).
HoldsLAOBJ0001LAOBJ0005The candidate physical files.

LAOBJ0001–LAOBJ0005 — candidate objects

CandidateAge (days)Class @ policy 30Outcome
LAOBJ00015KEEPFile remains in LAWORK.
LAOBJ000240PURGEDLTF'd; +204800 bytes reclaimed.
LAOBJ000312KEEPFile remains in LAWORK.
LAOBJ000491PURGEDLTF'd; +465920 bytes reclaimed.
LAOBJ000530KEEPBoundary (age = policy, not strictly over) — kept.

Each candidate is a real CRTPF FILE(LAWORK/LAOBJnnnn) RCDLEN(80), so a DLTF is a genuine state change. Reclaimed bytes = age × 5120; total 204800 + 465920 = 670720.

Source physical files

ObjectTypeMembers
LIBAUD/QREXSRCSource PF (RCDLEN 112)LAMAIN, LACLASS, LASELFTEST.
LARPTLIB/QREXSRCSource PF (RCDLEN 112)LARPT (resolved via *LIBL).
LIBAUD/QCLSRCSource PF (RCDLEN 112)LADRIVER.

Relationships

E. Operations Runbook ↑ top

E.1 Run it

  1. Confirm the job's library list includes LIBAUD and LARPTLIB (the latter so LARPT resolves via *LIBL), with CURLIB = LIBAUD.
  2. Set the retention threshold: CHGDTAARA DTAARA(LIBAUD/LAPOLICY) VALUE('30').
  3. Submit the driver: SBMJOB CMD(CALL PGM(LIBAUD/LADRIVER)) JOB(LIBAUDIT).
  4. Read the joblog transcript (the SAY lines) for the run's phases.

Pre-checks: confirm LAPOLICY holds the intended threshold; confirm LARPTLIB is on the library list (otherwise LARPT will not resolve).

Post-checks — the healthy transcript:

The line selftest: no trap fired (unexpected) must be absent — its presence would mean the LEAVE-name boundary did not fire, which is a real defect. Likewise any unexpected DLTF failure for ... line indicates a purge did not complete.

E.2 Verify committed state

Because the utility persists its outcome as real objects, verification does not depend on the transcript:

  1. Confirm LAWORK exists (created by the driver).
  2. Confirm the kept objects remain: LAOBJ0001, LAOBJ0003, LAOBJ0005 (CHKOBJ OBJ(LAWORK/LAOBJ0001) OBJTYPE(*FILE), etc.).
  3. Confirm the purged objects are gone: LAOBJ0002, LAOBJ0004 no longer exist.
  4. Read the summary: DSPDTAARA DTAARA(LIBAUD/LARESULT) must read exactly P=2 K=3.

These are precisely the assertions the test/libaudit.mjs harness makes against an independent hand-derived oracle (purged=2, kept=3, reclaimed 670720 bytes, LARESULT = "P=2 K=3").

E.3 Re-run behaviour

SituationBehaviourAction
Re-submit LADRIVER after a completed runKept objects (LAOBJ0001/3/5) still exist, so their CRTPF collides (object already exists) and trips the same ADDRESS COMMAND error handler mid-scan.Safe: the handler resumes, the sweep completes, and LARESULT is overwritten with the same true counts (P=2 K=3). The purge set (0002/0004) is never resurrected.
LAWORK missingDriver CHKOBJ fails (SFF9801), MONMSG catches it, CRTLIB creates it.Automatic — no operator action.
LARESULT missingExec CHKOBJ non-zero, then CRTDTAARA creates it before CHGDTAARA.Automatic — first run creates it.
LARPTLIB not on *LIBLLARPT cannot be resolved; the sweep fails at the report step.Add LARPTLIB to the library list and re-run.
Policy changedThe classification boundary moves; more or fewer candidates purge.Update LAPOLICY and re-run; the summary reflects the new counts.
The purge outcome is deterministic for a fixed policy and candidate set: an already-purged object cannot be re-deleted, and a kept object stays kept, so the committed LARESULT is stable across re-runs at the same policy.

F. Developer Reference ↑ top

The complete surface: one CL driver and four REXX execs, grounded in the rexx-app/src/*.txt members. This section documents each routine and which REXX/400 features it exercises.

F.1 LADRIVER (CL program, LIBAUD/QCLSRC)

PGM — no parameters
The batch entry point. Declares &POLAGE (CHAR 6), &RUNMODE (CHAR 10), &PARMSTR (CHAR 30).
RTVDTAARA DTAARA(LIBAUD/LAPOLICY) RTNVAR(&POLAGE)
Fetches the retention days. This is the crux of the bridge: RTNVAR() only binds inside a structured CL program, never from a bare REXX command clause — so the policy is fetched here and handed to REXX as a PARM token.
CHKOBJ OBJ(LIBAUD/LAWORK) OBJTYPE(*LIB) / MONMSG SFF9801 / CRTLIB
Ensures the scratch library exists (create-if-missing via monitored message).
CHGVAR &PARMSTR VALUE(&POLAGE *BCAT &RUNMODE) / STRREXPRC SRCMBR(LAMAIN) SRCFILE(LIBAUD/QREXSRC) PARM(&PARMSTR)
Builds the 'policyAge runMode' token with *BCAT (blank-cat) and starts the REXX procedure.
CHKOBJ OBJ(LIBAUD/LARESULT) OBJTYPE(*DTAARA)
Proves the exec's committed state independently of the REXX SAY transcript — the driver confirms the summary data area the exec must have created.

F.2 LAMAIN (main REXX exec, LIBAUD/QREXSRC)

parse arg polage runmode / polage = polage + 0
PARSE ARG splits the PARM token; the + 0 de-pads the zoned 6-char policy.
say ... || polage || 'd mode=' || runmode
String abuttal (a || b || c) building the startup line.
pull ovr / if ovr = '' then ...
Awaited external PULL for an operator card; empty in batch and treated as "no override".
call on error name handler
Registers the CALL-driven host-command error trap; the handler: label returns, resuming after the failing clause. Reports CONDITION('C')/CONDITION('I')/rc.
'CHKOBJ OBJ(LAWORK/LASTALEPRV) OBJTYPE(*FILE)'
Preflight probe of a never-present object — genuinely fires the CALL ON handler (RC=30) before the sweep, exercising the trap for real (not dead code).
do i = 1 to n ... CRTPF / laclass / DLTF / select
The sweep loop: create each candidate, classify with LACLASS, and on PURGE run a real DLTF guarded by if rc = 0, accumulating reclaimed bytes; a SELECT ... WHEN ... OTHERWISE routes PURGE vs KEEP.
say 'reclaimed bytes (exponential): ' || format(bytesreclaimed, , , 3, 0)
FORMAT with expp=3, expt=0 forces exponential output (6.70720E+005).
finalrpt = larpt(reportline)
Calls the external report routine resolved along *LIBL (not in this source file).
CHKOBJ / CRTDTAARA / CHGDTAARA LARESULT
Persists the run summary via the CL bridge, using a q = "'" quote var to embed a literal quote without doubling risk.
signal on syntax name selftesttrap / call laselftest / signal off syntax / exit 0
Runs the LEAVE-name self-test in an isolated CALL under a SYNTAX trap; the trap (selftesttrap:) reports cond=SYNTAX inst=SIGNAL rc=40 and exits cleanly.

F.3 LACLASS / LARPT / LASELFTEST (external routines)

LACLASS (LIBAUD/QREXSRC, SAME source file as LAMAIN)
Pure classifier: parse arg oage, pol; returns 'PURGE' when oage > pol, else 'KEEP'. Exercises same-source-file external-routine resolution and arithmetic. The strict > is what keeps the age-30 boundary candidate.
LARPT (LARPTLIB/QREXSRC, DIFFERENT library, resolved via *LIBL)
parse arg raw; tagged = changestr('LAOBJ', raw, 'OBJ#') redacts every object prefix; countstr('PURGED;', tagged) and countstr('KEEP;'…)—actually countstr('KEPT;', tagged)—total the outcomes; returns the one-line AUDIT REPORT [...] totals purged=n kept=n. Exercises cross-library *LIBL name resolution plus CHANGESTR/COUNTSTR.
LASELFTEST (LIBAUD/QREXSRC, SAME source file)
Deliberately does leave ghostloop with no enclosing DO named GHOSTLOOP, raising REXX error 28. As a failing external routine it surfaces to the caller as error 40 (SYNTAX class), proving the engine's LEAVE-name boundary is loud and the app can depend on it. The return 'unreachable' is never reached.

F.4 REXX/400 feature map

FeatureWhereWhat it proves
CL→REXX data-area bridgeLADRIVER RTVDTAARA→PARM→LAMAINRTNVAR binds only in a CL program; policy handed to REXX as a token.
PARSE ARG / PULL / PARSE LINEINLAMAIN lines 12, 19PARM splitting; empty external PULL handled as "no override".
String abuttal (a||b||c)LAMAIN say linesConcatenation without intervening blanks.
FORMAT(n,,,expp,expt)LAMAIN line 86Exponential/scientific formatting (6.70720E+005).
CHANGESTR / COUNTSTRLARPTRedaction and substring counting built-ins.
CALL ON ERROR + CONDITIONLAMAIN handlerHost-command RC trap; CONDITION('I')='CALL', resumes after clause.
SIGNAL ON SYNTAX + CONDITIONLAMAIN selftesttrapSyntax-class trap; CONDITION('I')='SIGNAL', distinct from CALL recovery.
Same-srcfile external CALLLACLASS, LASELFTESTRoutine resolution within the caller's own source member.
*LIBL external CALLLARPTSTRREXPRC walks the library list to a different library's QREXSRC.
LEAVE-name error 28LASELFTESTUnmatched LEAVE name surfaces loudly (error 28→40 to caller).
ADDRESS COMMAND to CLLAMAIN (CRTPF/DLTF/CHKOBJ/CRTDTAARA/CHGDTAARA)Real object state driven entirely from REXX host commands.

G. Glossary ↑ top

ADDRESS COMMAND
The REXX default host-command environment on IBM i: a quoted clause is passed to CL for execution, with the result reflected in the RC special variable.
CALL ON / SIGNAL ON
Two REXX condition-trap styles. CALL ON runs a handler that RETURNs and resumes after the failing clause (CONDITION('I')='CALL'); SIGNAL ON transfers control to a label and does not resume (CONDITION('I')='SIGNAL').
CHANGESTR / COUNTSTR
REXX string built-ins: CHANGESTR(needle, haystack, new) replaces every occurrence; COUNTSTR(needle, haystack) counts them. Used by LARPT to redact and total.
Candidate object
An object considered for retention. Here, a real CRTPF file (LAOBJ0001…) in LAWORK with a known synthetic age.
CONDITION(option)
REXX built-in returning trap context: 'C' the condition name (ERROR/SYNTAX…), 'I' the instruction that raised it (CALL/SIGNAL).
Data area (*DTAARA)
A small named IBM i object holding a value. LAPOLICY supplies the policy (input); LARESULT holds the run summary (output).
DLTF / CRTPF
CL commands to delete / create a physical file. The sweep CRTPFs candidates and DLTFs the purged ones — genuine state changes.
FORMAT
REXX numeric-formatting built-in. With exponent-place arguments it produces scientific notation (6.70720E+005).
LEAVE-name (error 28)
A LEAVE loopname with no matching enclosing loop is a REXX error 28. LASELFTEST triggers it on purpose to prove the boundary is enforced.
Policy / retention threshold
The age in days (LAPOLICY) above which an object is purged. An object exactly at the threshold is kept (strict >).
PULL / PARSE LINEIN
REXX input from the external queue/stream. In batch there is no stacked line, so it resolves to an empty string — here handled as "no operator override".
Purge / Keep
The two classifications: PURGE (age strictly over policy → delete) or KEEP (retain). Decided by LACLASS.
STRREXPRC
Start REXX Procedure — the IBM i command that runs a REXX source member, optionally with a PARM token. LADRIVER uses it to launch LAMAIN.
*LIBL resolution
Finding an object by name along the job's library list rather than a qualified library. STRREXPRC walks *LIBL to resolve LARPT in LARPTLIB/QREXSRC.