Retroactive Rate Changes: When You Update a Salary Backwards
A VP of Engineering walks out of a Q1 review and announces an 8% raise for 12 backend engineers, effective March 1. It's now May 18. Three months of finance reports already shipped to the board with the old rates baked in. HR has two options: pretend the raise started today, or retroactively update March, April, and May. Most engineering finance tools force option one. PanDev Metrics supports option two, and the Sarbanes-Oxley Act of 2002 is the reason it has to be done carefully.
This is one of the few areas where our product genuinely diverges from LinearB, Jellyfish, and Code Climate Velocity. Those tools were built around forward-only rate models. PanDev's UserRate table is bitemporal: every rate has a startPeriod and endPeriod, and the OverheadCoefficientFullRecalcCronJob will replay activity events through new rate × overhead K when historical rows change. That's powerful. It's also exactly the kind of capability that auditors look at twice.
{/* truncate */}
The full cascade triggered by a single UserRate row dated in the past. Every dependent cost row replays.
Why most tools refuse to do this
LinearB's cost module uses a snapshot per month. Once February closes, the rate that was active when reports ran is frozen. Same for Jellyfish's allocation engine. Their reasoning is defensible: if you let people edit history, you can edit your way out of bad numbers.
The problem is that real compensation doesn't respect the closing date. Salary review cycles run quarterly or semi-annually, then approvals stack up in HR, then the new rate is communicated weeks later with retroactive effect. The WorldatWork 2024 Salary Increase Survey reports that 62% of US companies grant retroactive effective dates on at least one merit cycle per year, with average backdating of 47 days. If your finance system can't model that, your "actuals" are systematically wrong by a quarter of a quarter, every single quarter.
PanDev's design choice was to make retroactivity native and the audit trail loud. You can change history. Every change is timestamped, attributed to a user, and visible in the rate version panel. SOX Section 404 requires "internal controls over financial reporting", and that's exactly what the audit log delivers.
What UserRate looks like under the hood
Each user has a list of rate rows in the UserRate table:
user_rate (
id,
user_id,
department_id,
amount,
rate_type ENUM('HOURLY', 'MONTHLY'),
start_period DATE,
end_period DATE, -- nullable; NULL = current
created_by,
created_at
)
A user with a single $5,000/month MONTHLY rate from January 1, 2026 has one row with start_period = 2026-01-01, end_period = NULL. When HR enters a new rate effective March 1 at $5,400, PanDev does two things atomically:
- Closes the existing row by setting
end_period = 2026-02-28. - Inserts a new row with
start_period = 2026-03-01,end_period = NULL,amount = 5400.
If the new row is dated in the past relative to today's date, the system flags it as a retroactive insert and queues OverheadCoefficientFullRecalcCronJob for the affected (department_id, year, month) tuples.
The recalculation cascade, step by step
Once the cron job picks up the queued recalc, the chain that fires is mechanical and exhaustive.
Table 1: What touches what when a retroactive rate lands
| Step | Entity recomputed | Why |
|---|---|---|
| 1 | UserRate history closed/inserted | New row reflects the retroactive raise |
| 2 | OverheadCoefficient per (dept, year, month) | taskTotal and nontaskTotal rise because dev cost portion grew |
| 3 | mv_cost_per_task_daily materialized view | Each task's loaded cost replays through new rate × new K |
| 4 | mv_cost_per_feature_monthly materialized view | Feature-level rollups update from refreshed task costs |
| 5 | Department / project cost reports | Aggregations refresh from the new MV state |
| 6 | Plan-vs-Actual variance line RETRO_RATE_DELTA | Variance is decomposed so the retro change doesn't pollute scope-creep numbers |
| 7 | Audit log entry | User, timestamp, before/after rate, dollar delta per affected month |
The recalc is idempotent: re-running it on the same range produces the same numbers. That's a non-negotiable property for SOX compliance, where auditors need to be able to reproduce the figures from raw events.
The UI: where HR actually does this
The flow is /dashboard/employees/salary-rate/:employeeId. The history panel shows every prior rate row. The "New Rate" dialog asks four things: effective date, amount, HOURLY/MONTHLY toggle, and a free-text justification. Before the user clicks confirm, the dialog renders an impact preview: how many already-finalized months will be touched, the dollar delta on taskTotal, and whether any of those months had been "locked" by a finance close.
That last point is the key control. Months in LOCKED_PERIOD status (set by Finance after month-end close) require an additional approval. A finance role has to co-sign the retroactive change. The default config requires two-person approval for any retro rate that crosses a locked boundary. AICPA's Statement on Auditing Standards 122 treats this two-person rule as the baseline expectation for any restatement workflow.
Worked example: the 8% retro raise
Back to the VP of Engineering. Twelve backend engineers, average direct rate $42/hour, 8% raise effective March 1, processed on May 18. Department K was 0.41 in March, 0.43 in April, 0.45 in May (we cover the seasonality in per-month K).
HR enters 12 new UserRate rows with start_period = 2026-03-01. The cron job queues recalc for (dept_id=engineering, 2026, 3..5). Within ~90 seconds the cascade completes.
Table 2: March 31 cost report, original vs replayed
| Cost line | Run on April 1 (pre-retro) | Run on May 19 (post-retro) | Delta |
|---|---|---|---|
| Direct dev cost (12 engineers, March) | $80,640 | $87,091 | +$6,451 |
taskTotal (March) | $58,866 | $63,576 | +$4,710 |
nontaskTotal (March) | $21,774 | $23,515 | +$1,741 |
| Overhead K (March) | 0.410 | 0.405 | -0.005 |
| Loaded cost per task hour | $59.22 | $63.74 | +$4.52 |
| Total department cost (March) | $113,702 | $122,562 | +$8,860 |
| Total cost (Mar+Apr+May) | $345,815 | $373,732 | +$27,917 |
Two things to notice. First, K actually went down slightly because adminSalary (CTOs, EMs, DevOps) didn't change while taskTotal + nontaskTotal rose. The overhead became a smaller fraction of a larger denominator. Second, the total delta of $27,917 is not a budget surprise: it's exactly 8% of the prior three months of direct cost for those 12 engineers, plus the K-blended overhead reweighting. The number reconciles.
A CFO who runs the March 31 cost report today gets $122,562. The same report run on April 1 returned $113,702. Both are correct. The audit log explains the difference in one sentence: "Retroactive rate increase of 8% applied to 12 engineers (employee_ids 401–412) on 2026-05-18 by user a.pan@pandev.io."
How this connects to the broader cost stack
The recalculation cascade only works because the whole cost engine is built bitemporally. If you've read our post on loaded hourly rate, you know that the loaded rate is direct × (1 + K), and K itself depends on the same taskTotal/nontaskTotal that just got recomputed. If you've read overhead coefficient, you know K is monthly per department, never averaged.
The retroactive recalc respects both: every month gets its own K replay, and every loaded rate is rebuilt from the new direct rate. It's also why this couldn't be a simple "multiply the affected reports by 1.08" shortcut. The K shifts when direct cost shifts, so the propagation has to actually replay event data, not patch totals.
Where the audit trail lives
PanDev keeps three layers of evidence for any retro rate change:
UserRateversioning. Old row remains with its closedend_period; new row exists alongside. You can query "what was Maria's rate on March 15?" against any historical timestamp and get the answer that was correct at that timestamp.AuditLogEntry. Append-only table withentity = 'UserRate',action = 'RETRO_INSERT',actor,before_state,after_state,dollar_delta_per_month,approver(if dual-control fired).- Variance attribution. The Plan-vs-Actual report adds a dedicated
RETRO_RATE_DELTAline so the retro change doesn't get conflated with scope creep or seasonal K spikes. We covered why that line matters in budget variance analysis.
Auditors reviewing a SOX 404 attestation typically ask three questions: who changed the number, when, and what did the number change from? The three layers above answer all three from the same event log.
What this is not
Retroactive rate change is a powerful tool that can be misused. Backdating rates to manipulate quarterly numbers is exactly the kind of thing SOX is designed to catch. See the SEC's enforcement actions on revenue restatement for the case law. The audit trail is your friend here, not your shield. Every retroactive change is timestamped, attributed, and visible to anyone with audit role access. Don't use this feature to clean up bad numbers. Use it to keep good numbers accurate.
A few patterns that should never happen and which the system flags:
- A retro rate decrease applied right before a contract bonus calculation runs.
- A retro rate change that crosses a fiscal year boundary without finance approval.
- A retro rate change on a single engineer's account by their own manager (self-approval is blocked).
The HR Technology Conference 2024 ran a panel specifically on compensation transparency tooling. The consensus from the panel, paraphrased from the published recap, was that visibility of retroactive change is more important than prevention. People will need to backdate sometimes. The bad version is invisible backdating.
Honest limit: what this doesn't fix
The recalc cascade keeps cost reports internally consistent. It does not retroactively fix decisions made on the old numbers. If the CFO presented Q1 results to the board on April 15 with the pre-retro rates, those numbers were correct as of April 15. They're now stale. The board materials don't get regenerated. That's a separate workstream, typically an addendum filed with the next quarterly review noting "Q1 dev cost restated upward by $19K following May salary review; restatement does not affect operating margin classification."
PanDev gives you the data to write that addendum. It can't write it for you. And it can't undo a board commitment made on numbers that have since been restated.
A closing position to defend
If your engineering finance tool doesn't support retroactive rate changes with full audit propagation, you don't actually have engineering finance. You have engineering bookkeeping with a quarterly correction problem. The interesting question isn't whether to allow retroactive changes; payroll reality forces them. The question is whether the system that absorbs them produces auditable history or quietly overwrites it. Pick the one that doesn't pretend the past is immutable.
