A developer changes a field length in one copy member. The change compiles clean. Nothing errors, nothing warns, and the nightly job runs that evening the way it always has.
Six weeks later somebody recompiles an unrelated program for an unrelated reason, and the new field length arrives inside it. A report prints wrong. The change that caused it landed a month and a half earlier, in a member nobody listed as a dependency of anything.
Application dependency mapping answers one question
Every dependency map on an AS/400 exists to answer the question a business asks before it approves any change. If we touch this, what else moves?
Enterprise IT gets a version of that question weekly. A field grows two digits for a new account number format. A vendor package needs a record format extended. In each case somebody has to name every affected program before the change board signs off, and the accuracy of that list decides whether the release goes quietly or produces an incident.
IBM i gives you a starting point at no cost. That starting point carries two documented limits and one undocumented hole, and the hole is where change estimates go wrong.
What DSPPGMREF actually reads
Display Program References is the tool most shops reach for first. IBM's command description states the scope: it "provides a list of the system objects referred to by the specified programs." It accepts *PGM, *SRVPGM, *MODULE and *SQLPKG, and it writes to outfile QADSPPGM under record format QWHDRPPR.
Point it at a whole library and you get a usable table:
DSPPGMREF PGM(PRODLIB/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(QGPL/PGMREF)
Each row names a program, the object it refers to, the library that object sits in, and the object type. For files, IBM also records how the program uses each one, covering input, output, update and unspecified access.
Query that file and you can answer several real questions in an afternoon. Which programs write to the customer master. Which files no program references at all. For a first pass at application dependency mapping, this beats reading source by hand.
Then the limits show up.
Limit one: the names are historical
IBM captures reference information at the moment the compiler creates the program. The command description carries the warning directly: "the referenced object names and libraries listed may be different than the actual names of the objects."
A program compiled in 2011 against a file that carries a different name today still reports the 2011 name. A program built when production lived in a different library still reports that library. Neither case produces an error anywhere. The row looks exactly like a correct row.
So a name in QADSPPGM is a claim about the past. Verifying it means checking that the object exists today under that name in that library, and that is a join nobody remembers to write.
Limit two: entries are never removed
The second limit does more damage, and IBM states it plainly. UPDPGM and UPDSRVPGM add entries when a developer updates an ILE program or service program, and IBM's description says that "entries are never removed."
Read that in the context of a thirty-year-old AS400 application. A file that a program stopped touching in 2014 still appears as a dependency in 2026. A subroutine somebody gutted and replaced left its old references standing. The file grows in one direction for the life of the object.
So the reference list over-reports. Every impact analysis built on it names programs that no longer care about the change, which sounds harmless until you count the cost. A team that regression-tests forty programs when the change touches eleven spends its release window on twenty-nine false positives. After that happens twice, people stop trusting the list, and somebody scopes the next change by asking the longest-tenured developer what he remembers.
The hole: copy members leave no trace
IBM documents both limits above, and a careful team can work around them. No system command works around the third problem, because nothing ever stored the information.
ILE RPG compiler directives insert records from other source members while the program compiles. The /COPY and /INCLUDE directives pull a member's text into the source stream, and the compiler builds one object out of the merged result. IBM's reference describes those directives as inserting records from other file members during a compile.
The compiler finds the member by walking the library list. It searches every occurrence of the named source file until it locates the member or runs out of places to look. Nothing in that process leaves a marker inside the compiled object naming which member it used.
DSPPGMREF reports objects. A copy member is a member inside a source physical file, and the running program never refers to it as an object. So the reference file cannot list it, and no amount of querying QADSPPGM will surface it.
The compile listing does show the expansion, and only on request. Source records of copy members appear in the listing when a developer specifies *SHOWCPY on the OPTION parameter of the compile command. The record of what a program included therefore sits in a spooled file somebody produced years ago, if anyone specified the option at all, for a compile nobody kept.
Why this fails silently
Three properties combine into the silent failure.
The first is reach. A single copy member holding a field definition serves hundreds of programs across several libraries. Change it once and you have changed all of them.
The second is timing. Editing the member changes nothing running today. Compiled objects keep the old definition until somebody recompiles them, and that happens on an unrelated schedule for unrelated reasons. Cause and symptom separate by weeks.
The third is silence. The compiler takes the first match in the library list. A member found in a development library ahead of production compiles without complaint. A shop with a shared source file and an inconsistent library list can build two programs from two different versions of the same include on the same afternoon, and every compile reports success.
None of that produces a warning. The system behaves exactly as IBM documents it.
The first-pass map you can build this week
You do not need a parser to find out how big this problem is on your machine. You need one command and a spreadsheet.
FNDSTRPDM scans the members of a source file for a string. Point it at each source file in scope and search for /COPY. What comes back is a rough include map: which members reference an include, and which include names show up most often.
Sort that output by include name and the shape of the risk appears immediately. The member at the top of that list is the one that touches the most programs, and it is almost always a field or record layout definition. That single name belongs on the agenda of your next change board.
Treat the result as a floor. The scan finds the directive text, and it cannot tell you which library the compiler actually resolved each include from, which is the ambiguity that produces two versions of the same definition in production.
Building a map that holds copy members
A real dependency graph on IBM i has to read source. Three requirements follow from everything above.
Resolve includes the way the compiler resolves them. A parser has to search the source files in library-list order and record which member it found, because the answer depends on the list in effect at compile time.
Name every include the parser could not find. An unresolved include is the most valuable row in the whole inventory. It means the member is gone or the library list is wrong, and both conditions produce programs nobody can rebuild.
Walk the graph in both directions. A software dependency graph that answers "what does this program use" is half a map. Impact analysis software has to answer the reverse question, listing every program that reaches a given field or member, and that index only exists when you build the graph from parsed source.
Our RPG code analysis work starts from these three requirements, because a dependency map assembled from compiled objects alone inherits every limit on this page.
How AS/Forward builds the graph
AS/Forward ingests source directly from the IBM i and reads it across six RPG dialects. Its proprietary ingestion layer resolves copy members and interconnections before any model touches the code, and it flags every include it could not resolve as a named exception. Golden Path Digital holds one US patent pending on that method.
The graph feeds three views a change board can use.
The program view opens a single program. It shows the source beside the dependency edges the parser resolved, plus a plain-English description the parser wrote from that same source. Warnings sit next to it wherever parser confidence ran low, which tells a developer where to look first.
The migration view scopes a proposed change. Name a field or an object, and the tool shows what else moves across the whole ingested library, well past the single program somebody happened to open.
The readiness view sorts the library by parser confidence. Programs the ingestion layer read cleanly separate from programs that need a developer, so a project plan can sequence the automatic work ahead of the manual work.
Everything runs where your security review needs it, including entirely inside your own network on hardware you control. Source never leaves the building. The AS/Forward page has the detail.
One question for your next change board
Take the next field change your team scopes and ask which member defines the field. If the answer is a copy member, ask who produced the list of affected programs and what tool produced it.
An answer built on DSPPGMREF has told you two things. The list omits every program that reaches the field through an include, and it carries stale entries the system has never removed. A team can ship on that list. It cannot predict what shipping will do.
Golden Path Digital runs an IBM i modernization assessment that produces the resolved graph for your libraries, including the unresolved copy member count. Start there.