04 · Rules
Procedural code hides outcomes. Rules state them.
The Standard calls business rules the stable part. Years of code changes have blurred them into branches, flags and config. This chapter pulls them back out as plain sentences a business person can correct and a test can cite.
COBOL · at Tidewater
Nine lines, two rules and a secret.
This paragraph has calculated Tidewater's late fees since 1987. Two details decide real money: COMPUTE without ROUNDED drops the extra digits, and one bank is written straight into the code.
2200-ASSESS-FEE.
IF PMT-BANK-ID = '0417'
MOVE ZERO TO WS-LATE-FEE
ELSE
COMPUTE WS-LATE-FEE = PMT-AMT-DUE * 0.05
IF WS-LATE-FEE < 10.00
MOVE 10.00 TO WS-LATE-FEE
END-IF
END-IF.
## Parameters | Parameter | Type | Meaning | |------------|---------|---------------------------------| | fee waived | boolean | The bank's loans have no late | | | | fee (Granite Valley: yes) | ## Rules - LPF-003: A late fee is 5% of the amount due, with any fraction of a cent dropped, and is never less than $10.00. - LPF-004: A bank with the fee waived parameter set is never charged a late fee.
Worked case: $1,234.70 × 5% = $61.735, so the fee is $61.73. A rounding build charges $61.74.
The hard-coded bank
Bank 0417 is Granite Valley, waived by a 1998 agreement that nobody had filed anywhere else. Forking the process per bank is forbidden by the Standard, so a single IF becomes a tenant parameter, and the agreement finally has somewhere to be written down.
RPG on IBM i · at Tidewater
A system you're replacing still has rules.
KeelTrack came with the 2009 purchase of Keel Street Servicing, and a product will replace it. A vendor's feature list can't tell you whether Tidewater's real rule still holds, but a rule test can.
chain (loanNo) COLLATPF; if %found(COLLATPF) and CVGEXP < today - %days(30); LAPSEFLG = 'Y'; update COLLATR; endif;
- CIN-001: A loan's collateral counts as uninsured once its coverage has been expired for more than 30 days.
Becomes an acceptance test for the product. Older RPG hides the same logic in fixed-format indicators, so have someone who knows the codebase confirm it.
Java on the platform · at Tidewater
The rule was on the server, not in the repository.
Modern-looking code can still hide its rules. BankLink's payoff quote takes its validity period from a properties file that exists only on the WebSphere server. Its rounding, however, is explicit, so the two rules needed different evidence.
BigDecimal perDiem = balance.multiply(rate)
.divide(DAYS_360, 2, RoundingMode.HALF_UP);
Date goodThrough = addBusinessDays(today,
config.getInt("quote.valid.days"));
# on the server only
quote.valid.days=10
- PQ-001: A payoff quote is good through 10 business days after the day it is issued. - PQ-002: A payoff quote's daily interest is the balance times the annual rate divided by 360, rounded half-up to the cent.
These money rules differ: LPF-003 truncates and PQ-002 rounds. Each states its own (MIG-015).
Arithmetic
Money is where rewrites quietly go wrong.
| Behavior | Source | What the rule must say |
|---|---|---|
| Truncation | COMPUTE without ROUNDED; RPG eval without (h) | Drop or round, and to how many places |
| Field size | A result silently losing high-order digits | The largest valid value, and what happens above it |
| Intermediate precision | Compiler options, such as COBOL ARITH | The order of steps, and any rounding between them |
| Day counts | Hand-written 30/360 routines | The convention, by name, with a worked case |
What you leave behind · at Tidewater
A decision not to do something belongs in the spec too.
A migration is full of deliberate omissions. The evidence ledger is deleted with the source, so an omission recorded only there leaves nobody able to answer "why doesn't the new system do what the old one did?"
If you can test it, make it a rule, as with LPF-005. If you can't, because it's something not built, it goes in the spec's Out of scope section, where an AI assistant won't ask about it or add it.
## Out of scope - Posting fees to the ledger. Anchor Core does this (SCOPE-001). - Printing the past-due listing. It went unread for 30 months. - Payment plans for late borrowers. Banks arrange these by phone today. Reopen if volume grows.
Rules
MIG-014 to MIG-017, MIG-039
- MIG-014
A rule states the business outcome and never names the paragraph, step, or program it came from; that link belongs in the evidence ledger.
- MIG-015
Every calculation rule states its precision, rounding, and order of operations exactly.
- MIG-016
A hard-coded customer, date, or code value becomes a tenant parameter, a stated rule, or a retired behavior, by a recorded decision.
- MIG-017
A capability marked replace still gets rules, because they become the acceptance test for the product.
- MIG-039
A behavior deliberately not carried forward is recorded in the spec's Out of scope section, so the decision outlives the evidence ledger.
# Rule card — <CODE>-nnn - **Rule:** <one plain sentence stating an outcome; no program, step or paragraph names> (MIG-014) - **Lives in:** <record type spec, if always true of the data; process spec, if true of one procedure> - **Evidence:** EV-nnn, EV-nnn (MIG-005) ## If it calculates (MIG-015) - **Precision:** <digits kept at each step> - **Rounding:** truncate | half-up | half-even - **Order of operations:** <what happens first> - **Worked case:** <input> gives <output> ## Tests - **Rule test:** Test<CODE>nnn — <what it asserts> - **Captured cases:** <where they are, and how many> (MIG-029) ## Decision - **Keep behavior as found?** yes | no → difference DIFF-nnn - **Decided by:** <data steward or process owner> — YYYY-MM-DD