The Strata: Read the Copybook, Then Let the Data Testify
Turn PIC clauses into a data dictionary, profile the relic’s file until its tells surface, and date its strata — the familiar drafts, the data confirms.
Your choice is remembered across quests and rewrites the commands below to match.
The relic has a data dictionary already. It is called the copybook, and it was written for the machine: six bytes here, nine digits there, a decimal point that exists only in the program’s mind. Your job in this chapter is to write the one for humans — and to refuse every meaning in it that the data has not confirmed. The Archives keep records; the Librarian decides what they mean.
The real-world skill: decoding a fixed-width record layout into a data dictionary, and using the data itself — not a familiar’s confidence — to validate it.
📖 The Legend Behind This Quest
A relic’s data outlives its documentation by decades. The copybook survives because the program will not compile without it; the meaning of each field survives only in heads, and heads retire. Two things always show through when you profile a relic’s data: dates written with two-digit years that straddle a century, and codes in a status column that no scroll explains. The guild calls them the tells, and the discipline of this chapter is to make a familiar draft the dictionary fast, then let the data testify before a single meaning is believed.
🎯 Quest Objectives
Primary Objectives
- Decode the copybook by hand — compute every field’s offset and length from its
PICclause, and explain the implied decimal - Profile the data by script — parse the copybook programmatically and report distinct values, ranges, and the two tells for every field
- Draft the dictionary with a familiar — then reject every meaning that does not cite the copybook, the code, or the profile
- Date the strata — build a table of the relic’s layers from its header comments and its Chronicle
Mastery Indicators
- You can say, without running anything, which byte
INV-STATUSstarts at and why - You can show that
000125000and the report’s1250.00are the same number and where the point went - Your dictionary marks the difference between a meaning you read and a meaning you inferred
🗺️ Quest Prerequisites
- Chapter I complete — you need the relic’s files and its report in your
relic-raisersfolder. - A little Python — string slicing (
rec[34:40]) and reading a file line by line are the only tricks used. - Your familiar — the same
claude -pspells as before; any assistant will do.
🧙♂️ Chapter 1: Reading PIC — the Copybook by Hand
⚔️ Skills You’ll Forge
Xversus9, and whatVmeansREDEFINESas two names for the same bytes- Byte offsets as the map of a record
Lay the copybook’s fields end to end. X(n) is n characters; 9(n) is n digits stored as characters too; V marks where a decimal point would be and costs nothing on disk; REDEFINES gives the same bytes a second set of names and advances nothing. Walk it once with a pencil:
| Field | PIC | Offset | Length | Notes |
|---|---|---|---|---|
INV-CUST-ID |
X(6) |
0 | 6 | text |
INV-CUST-NAME |
X(20) |
6 | 20 | text, space-padded |
INV-NUMBER |
X(8) |
26 | 8 | text |
INV-DUE-DATE |
9(6) |
34 | 6 | YYMMDD — two-digit year |
INV-DUE-YY / INV-DUE-MMDD |
99 / 9(4) |
34 / 36 | 2 / 4 | REDEFINES the six bytes above; adds no bytes |
INV-AMOUNT |
9(7)V99 |
40 | 9 | nine digits, point implied two from the right |
INV-STATUS |
X |
49 | 1 | one code, undocumented |
FILLER |
X(30) |
50 | 30 | reserved; the comment says the layout is shared |
Prove the implied decimal with the report from Chapter I: INV10001 carries 000125000 in the data and the relic printed 1250.00. Nine digits, the point inserted before the last two, leading zeros dropped by the edit picture Z(7)9.99. Nothing in the file says “1,250” — only the program knows, and now so do you.
🔍 Knowledge Check
- What byte does
INV-STATUSoccupy, and how did you get there? - If a future program wrote a real decimal point into
INV-AMOUNT, what would the relic read? - Why does the
REDEFINESrow add nothing to the running offset?
🧙♂️ Chapter 2: Let the Data Testify — the Profiler
⚔️ Skills You’ll Forge
- Parsing
PICclauses programmatically - Profiling fixed-width data field by field
- Recognizing the two tells
A pencil scales to one copybook. A script scales to the Empire’s. Save this as profile_relic.py; it parses the copybook’s PIC clauses into offsets, then walks the data file and reports what each field actually contains — including the two tells that every relic hides.
#!/usr/bin/env python3
"""profile_relic.py — read a copybook, then let the DATA testify.
Parses the PIC clauses in a COBOL copybook into (name, offset, length, kind),
then profiles every field across a fixed-width data file: distinct values,
min/max, and the two relic tells — two-digit years on both sides of a pivot,
and status codes nobody documented. Usage: profile_relic.py COPYBOOK DATAFILE
"""
import re
import sys
from collections import Counter
PIC_RE = re.compile(r"^\s*(\d\d)\s+([\w-]+)\s+(?:(REDEFINES)\s+[\w-]+\s+)?PIC\s+([X9SV()\d]+)\.", re.I)
REDEF_RE = re.compile(r"^\s*(\d\d)\s+([\w-]+)\s+REDEFINES\s+([\w-]+)\.", re.I)
def pic_length(pic):
"""X(6) -> 6, 9(7)V99 -> 9 (V is an implied point: no bytes)."""
total = 0
for sym, rep in re.findall(r"([X9])(?:\((\d+)\))?", pic.upper()):
total += int(rep) if rep else 1
return total
def parse_copybook(path):
fields, offset, in_redefines, redef_level = [], 0, False, None
for line in open(path):
if len(line) > 6 and line[6] == "*":
continue # a comment line: column 7 holds the asterisk
body = line[7:72] if len(line) > 7 else ""
m = REDEF_RE.match(body)
if m:
in_redefines, redef_level = True, int(m.group(1))
continue
m = PIC_RE.match(body)
if not m:
continue
level, name, redefines, pic = int(m.group(1)), m.group(2), m.group(3), m.group(4)
length = pic_length(pic)
if redefines:
continue # REDEFINES with its own PIC: re-names bytes already counted
if in_redefines and level > redef_level:
continue # sub-fields of a REDEFINES group share bytes already counted
in_redefines = False
kind = "numeric" if pic.upper().lstrip("S").startswith("9") else "text"
fields.append((name, offset, length, kind))
offset += length
return fields, offset
def main(copybook, datafile):
fields, reclen = parse_copybook(copybook)
records = [l.rstrip("\n").ljust(reclen) for l in open(datafile)]
print(f"{copybook}: {len(fields)} fields, record length {reclen}; {datafile}: {len(records)} records\n")
print(f"{'FIELD':<16}{'OFF':>4}{'LEN':>4} {'KIND':<8}{'DISTINCT':>9} MIN .. MAX")
for name, off, ln, kind in fields:
values = [r[off:off + ln] for r in records]
distinct = Counter(values)
lo, hi = min(values), max(values)
print(f"{name:<16}{off:>4}{ln:>4} {kind:<8}{len(distinct):>9} {lo!r} .. {hi!r}")
if name.endswith("-DATE") and ln == 6:
years = sorted({v[:2] for v in values})
print(f"{'':16} two-digit years present: {years} — which century is each? (look for a pivot in the code)")
if name.endswith("-STATUS"):
print(f"{'':16} status distribution: {dict(distinct)} — every code needs a documented meaning")
if __name__ == "__main__":
main(*sys.argv[1:3])
Cast it on the relic’s files:
python3 profile_relic.py INVREC.CPY INVOICES.DAT
INVREC.CPY: 7 fields, record length 80; INVOICES.DAT: 8 records
FIELD OFF LEN KIND DISTINCT MIN .. MAX
INV-CUST-ID 0 6 text 4 'C00101' .. 'C00412'
INV-CUST-NAME 6 20 text 4 'ACME FASTENERS ' .. 'PLATTE VALLEY DIST '
INV-NUMBER 26 8 text 8 'INV10001' .. 'INV10008'
INV-DUE-DATE 34 6 numeric 8 '260401' .. '991231'
two-digit years present: ['26', '99'] — which century is each? (look for a pivot in the code)
INV-AMOUNT 40 9 numeric 7 '000125000' .. '000980000'
INV-STATUS 49 1 text 2 '7' .. 'O'
status distribution: {'O': 7, '7': 1} — every code needs a documented meaning
FILLER 50 30 text 1 ' ' .. ' '
The script’s offsets match your pencil’s, which is the first thing to check whenever a tool reads a copybook for you. That check is not a formality: COBOL writes REDEFINES two ways, as the group this copybook uses and as a single field carrying its own PIC on the same line, and a reader that handles only the first counts the redefining field’s bytes twice. Every field after it then sits at the wrong offset and the record length comes out too long. Both forms are why the parser skips on redefines as well as on the group’s level. Then the tells. The date column holds years 26 and 99, and only the program’s pivot can say whether 99 is 1999 or 2099 — you saw in Chapter I that it chose 1999, and Chapter V will show what happens when a port forgets. The status column holds O and 7, and no scroll in the folder says what either means; the header comment says what 7 does, not what it is. On a real relic you will see a dozen codes and a comment for two of them. Write every code down; the ones without a meaning are questions for the Elders.
🔍 Knowledge Check
- The profiler reports
INV-DUE-DATEas numeric with eight distinct values. Which of those values would a port sort wrongly if it treatedYYas a plain number? - Why does the script skip the
INV-DUE-YYandINV-DUE-MMDDlines when computing offsets? - What would a
FILLERfield with more than one distinct value tell you about the “reserved” bytes?
🧙♂️ Chapter 3: The Dictionary — a Familiar Drafts, the Evidence Decides
⚔️ Skills You’ll Forge
- Prompting for a dictionary that separates read meanings from inferred ones
- Auditing every row against evidence
Save the profile to a file and hand the familiar everything it is allowed to know: the copybook, the program, and the data’s testimony. The spell tells it what a row must contain and what to do when it does not know.
python3 profile_relic.py INVREC.CPY INVOICES.DAT > profile.txt
cat INVREC.CPY ARAGE01.cob profile.txt | claude -p "Draft a data dictionary as a Markdown table with one row per field of INV-RECORD: FIELD, OFFSET, LENGTH, TYPE, MEANING, EVIDENCE. Under EVIDENCE cite the copybook line, the program paragraph, or the profile line that supports the MEANING. If you are inferring a meaning rather than reading it, prefix it with HYPOTHESIS. Do not invent meanings for values no source explains." > DATA_DICTIONARY.md
Print mode answers on standard output, so the redirect is what puts the draft on disk. Open the file and audit it. Every row must survive three questions: does the offset match the profiler, does the evidence exist where the row says, and is the meaning read or guessed? The dictionary that survives the audit looks like this — notice the two rows that are honest about what nobody knows:
| Field | Offset | Length | Type | Meaning | Evidence |
|---|---|---|---|---|---|
INV-CUST-ID |
0 | 6 | text | Customer key, C + five digits |
profile: 4 distinct, C00101..C00412 |
INV-CUST-NAME |
6 | 20 | text | Customer name, space-padded | copybook PIC X(20); report column NAME |
INV-NUMBER |
26 | 8 | text | Invoice key, unique per record | profile: 8 distinct in 8 records |
INV-DUE-DATE |
34 | 6 | numeric | Due date YYMMDD; YY < 50 is 20YY, else 19YY |
PROCESS-RECORD pivot; INV10006 prints 1999/12/31 |
INV-AMOUNT |
40 | 9 | numeric | Open amount, two implied decimals | PIC 9(7)V99; 000125000 prints 1250.00 |
INV-STATUS |
49 | 1 | text | 7 = excluded from aging, reported as DISPUTED; O = HYPOTHESIS “open” — no source defines it |
MOD 06/06 comment; EVALUATE first WHEN; profile {'O': 7, '7': 1} |
FILLER |
50 | 30 | text | Reserved; HYPOTHESIS used by ARINV05/ARSTM02 |
copybook header comment; profile: all spaces |
The row for O is the important one. Seven of eight records carry it, the program never mentions it, and a familiar will cheerfully call it “open”. That is a Plausible Ghost in miniature: probably right, entirely unproven, and the word HYPOTHESIS is what keeps it from being built into a port as fact.
🔍 Knowledge Check
- Which row’s meaning is read from code, and which is inferred from data? Name one of each.
- What would you need to see to remove the HYPOTHESIS mark from the
Ostatus? - Why hand the familiar the profile at all, rather than only the copybook?
🧙♂️ Chapter 4: Date the Strata
⚔️ Skills You’ll Forge
- Reading a relic’s history from its comments
- Consulting the Chronicle for the layers the comments forgot
Dig anywhere in a relic and you hit a decade. ARAGE01 announces its own strata in the header; write them down as a table in STRATA.md, oldest at the bottom the way a dig is drawn:
| Layer | Date | Change | Who | Where in the code |
|---|---|---|---|---|
| 3 | 06/2006 | Status 7 excluded from aging, reported separately |
“J.R./ACCTG” | EVALUATE, first WHEN; WS-TOT-DISPUTED |
| 2 | 11/1999 | Y2K window added, pivot 50 | unnamed | READ-PARM, PROCESS-RECORD |
| 1 | 03/1997 | Written; converted from RPG II | “D.M.” | the whole program |
On a real relic the comments lie by omission and the Chronicle fills the gaps. These three spells read the Chronicle of any file that has one; run them on your own repository and on the next relic you meet:
git log --oneline --follow -- ARAGE01.cob # every entry that touched the file
git log -S'STATUS 7' --oneline -- ARAGE01.cob # entries that added or removed a phrase (the pickaxe)
git blame -L '/EVALUATE TRUE/,+14' ARAGE01.cob # whose hand inscribed the bucket logic, and when
Your lab relic’s Chronicle has a single entry — “as found” — because you began it in Chapter I. That is itself a fact worth recording: a system whose history starts on the day you arrived has strata you can only recover from comments and people. Before you commit, add the two tells to the Unknowns list in EXPEDITION.md — what O means, and whether any due date before 1950 ever existed — so the interview guide for Chapter III is complete. Then commit the dictionary, the strata table, and the notes.
git add profile_relic.py profile.txt DATA_DICTIONARY.md STRATA.md EXPEDITION.md
git commit -q -m "strata: data dictionary with evidence, profiler, dated layers"
🎮 Mastery Challenge
Objective: a dictionary the port in Chapter V can be written from, and a strata map the Elders in Chapter III can correct.
profile_relic.pyruns clean and its offsets match your hand-computed tableDATA_DICTIONARY.mdhas seven rows; every MEANING has an EVIDENCE cell, and every inferred meaning says HYPOTHESISSTRATA.mddates all three layers and names the code each one lives in- Both tells — the century straddle and the undocumented
O— are written down as questions inEXPEDITION.md
🎁 Rewards & Progression
- 📜 Copybook Reader — you decoded every field and dated every stratum
- 🧮 Skill unlocked: reading
PICclauses into byte offsets and implied decimals - 🔬 Skill unlocked: profiling data to confirm or deny a dictionary
- +75 XP
🔁 Reproduce It
The profiler and its output above were run on 2026-09-14 against the exact INVREC.CPY and INVOICES.DAT from Chapter I, on Python 3.11 (any 3.10+ works; stock Ubuntu 24.04 ships 3.12). The dictionary table is the audited result of that run plus the relic’s own comments; the only two meanings marked HYPOTHESIS are the two that no source in the folder defines.
🗺️ Quest Network
graph LR
A["Ch. I — The Dig Site"] --> B["Ch. II — The Strata"]
B --> C["Ch. III — The Elders"]
click A "/quests/0011/relic-raisers-01-the-dig-site/"
click C "/quests/1111/relic-raisers-03-the-elders/"
classDef current fill:#1f6feb,stroke:#0b3d91,color:#fff;
class B current;
Chapters sit at different levels by design: the campaign runs through the levels, and each chapter also appears on its own level hub.
🔮 Next Adventures
The data has said everything it can. Two meanings are still marked HYPOTHESIS, and a comment names a person. Next you find the Elders and recover the reasons before they retire.
- ➡️ Next chapter: Chapter III — The Elders
- 🏺 Campaign hub: Epic Quest: The Relic Raisers
- 📚 Deeper in the Archives: Data Modeling
📚 Resource Codex
- GnuCOBOL Programmer’s Guide —
PICTUREsymbols,REDEFINES, andCOMP-3explained by the compiler’s own manual - Python
remodule — the runecraft the profiler uses to readPICclauses - Python
collections.Counter— the one-liner behind the status distribution
🕸️ Knowledge Graph
Structured wiki-links connect this quest to the IT-Journey knowledge graph. Open the Obsidian Graph View to explore connections.
Campaign hub: [[Epic Quest: The Relic Raisers]] Level hub: [[Level 0110 (6) - Database Mastery]] Previous: [[The Dig Site: Unearth the Relic and Name What You Do Not Know]] Next: [[The Elders: Recover the Reasons Before They Retire]] Obsidian docs: [[Obsidian Knowledge Graph and Wiki Links]]
🎁 Rewards
Badges
- 📜 Copybook Reader — decoded every field and dated every stratum
Skills unlocked
- 🧮 Reading PIC clauses into byte offsets and implied decimals
- 🔬 Profiling data to confirm or deny a dictionary
Features unlocked
- Continue The Relic Raisers campaign
🕸️ Quest Network
Click a node to open the quest · ⌘/Ctrl-click for a new tab · drag to reposition · scroll to zoom.
Referenced by
- Loading…