Oncology safety monitoring operates under intense regulatory scrutiny. Every data cut decision, every SAE narrative, every judgment about treatment-relatedness, every action taken in response to a safety signal — all of it must be traceable, time-stamped, and available for inspection. The audit trail question is not whether to maintain one, but how.
In most organisations, the answer is a mix of emails, meeting minutes, and a Word document assembled at the end of the analysis cycle. These are retrospective, manually curated, and impossible to cryptographically verify. If a regulator asks "who decided to retain that pneumonitis case and when?" the honest answer is usually "we think it was the medical monitor, sometime around month-end review."
regulog replaces this with a hash-chained audit trail built as the analysis runs. Every consequential action — data access, population decision, signal identification, medical monitor review, sign-off — is logged in real time and cryptographically linked to every entry before and after it. The result is an audit trail that is simultaneously a direct artefact of the analysis and a tamper-evident record of every decision that shaped it.
This post walks through a complete safety monitoring workflow for a Phase III NSCLC trial of a KRAS G12C inhibitor versus docetaxel.
The trial
NSCLC-KRASi-301 is a randomised, double-blind Phase III trial comparing a novel KRAS G12C inhibitor (400mg QD) against docetaxel (75mg/m²) in 400 patients with second-line non-small cell lung cancer harbouring KRAS G12C mutations. This safety analysis covers a scheduled data cut for Safety Monitoring Committee (SMC) review.
Opening the audit session
regulog_init() creates the session and writes the genesis record immediately, anchoring the hash chain. Study-level context — protocol, data cut, analysis set, governing committee — is captured as the first logged note, not as free text in a Word file:
log <- regulog_init(
app = "NSCLC-KRASi-301-safety-summary",
version = "1.0.0",
user = "jsmith",
path = "logs/audit_KRASi301_safety_v1.rlog"
)
log_note(log,
"Scheduled safety data cut for Safety Monitoring Committee (SMC) review.
Protocol: NSCLC-KRASi-301. Data cut: 2026-05-15. Analysis set: safety
analysis set (SAFFL = Y). SMC meeting: 2026-07-10."
)
Logging the data cut
The data cut date is one of the most frequently questioned items in a safety inspection — when exactly was it, who confirmed it, and what authority established it. In regulog this is a first-class log entry via log_note(), with no default text — a justification is always required:
log_note(log,
"Data cut date confirmed as 2026-05-15 per DMC charter Section 4.2.
All AEs with onset on or before 2026-05-15 included.
Lock confirmed by DM team (ref: DM-lock-20260518-001)."
)
This entry is immediately incorporated into the hash chain. Any subsequent modification — changing the date, altering the reference — breaks the chain and is detectable without reference to the original system.
TEAE incidence and Grade 3/4 analysis
The core safety tables are computed and logged with log_action() at the point of computation. The log entry records the exact result, not a description of what was planned:
log_action(log,
action = "compute_teae_incidence",
object = "TEAE incidence table by SOC",
reason = "Computed per SAP Section 6.1. KRASi: 187/200 (93.5%) with
>=1 TEAE | Docetaxel: 183/200 (91.5%)"
)
log_action(log,
action = "compute_grade34_ae",
object = "Grade 3/4 TEAE table",
reason = "Computed per SAP Section 6.2 and ICH E9. Grade 3/4 AEs: 94
events in KRASi arm, 112 in docetaxel arm"
)
Documenting a safety signal
This is where the difference between regulog and a Word document becomes most tangible. When a safety signal is identified — in this case, pneumonitis cases in the KRASi arm — the standard process is to email the medical monitor, hold a call, and then write up what was decided. The time between identification and documentation can be days or weeks.
With regulog, the signal identification, individual case documentation, and medical monitor decision are all logged sequentially in the same session, using log_note() for free-text rationale that doesn't fit a discrete action verb:
# Signal identified during analysis
log_note(log,
"Safety signal identified: pneumonitis — 14 cases in KRASi arm vs 2 in
docetaxel arm. Grade 3/4: 5 cases. Flagged for medical monitor review
per Safety Review Plan."
)
# Individual Grade 3+ cases documented
log_note(log,
"Pneumonitis case — USUBJID KRASi301-0047 | Grade 3 | Action: DRUG
WITHDRAWN | Related: RELATED"
)
# ... repeated for each case ...
# Medical monitor decision logged
log_note(log,
"Medical monitor reviewed all 14 pneumonitis cases (ref:
MM-review-20260620-001). Conclusion: incidence consistent with class
effect; benefit-risk remains favourable. Risk mitigation: enhanced
monitoring added to IB v4.2 (dated 2026-06-22)."
)
The timestamp on the signal identification entry and the medical monitor decision entry are cryptographically bound to every entry before and after them. The question "how long between signal detection and medical monitor review?" has a precise, verifiable answer.
Dual electronic sign-off
Oncology safety reports typically require both the biostatistician and the medical monitor to sign. Under 21 CFR Part 11 §11.100 and §11.200, electronic signatures must include the signatory's name, the date and time, and the meaning of the signature.
log_signature() resolves the signer identity from the session user set at regulog_init() — it cannot be overridden per call, by design, so a signature always reflects who actually opened that session. For two independent sign-offs, the pattern is two separate sessions writing to the same .rlog path — the file accumulates both signatures into one continuous, verifiable chain:
# Biostatistician sign-off — the session used throughout the analysis
log_signature(log,
"I confirm that the safety analyses were conducted in accordance with
SAP v2.1 and the SMC charter. All AEs, SAEs, and discontinuations are
correctly classified and tabulated."
)
# Medical monitor sign-off — independent session, same .rlog path
log_mm <- regulog_init(
app = "NSCLC-KRASi-301-safety-summary",
version = "1.0.0",
user = "medical.monitor",
path = log$path
)
log_signature(log_mm,
"I confirm that I have reviewed the safety data including all
pneumonitis cases and the integrated safety summary. The benefit-risk
profile remains favourable for KRASi 400mg QD."
)
Both signatures land in the same hash chain as the analysis that produced the results they are signing. You cannot sign one analysis and attach the signature to a different one — the chain would break.
Verifying integrity and exporting
# Verify directly from the file path -- covers both sessions' entries
verify_log(log$path)
# regulog: Log intact: 41 entries, chain unbroken
export_audit_trail(log$path,
format = "csv",
signed = TRUE,
path = "outputs/audit_KRASi301_safety_v1.csv"
)
What changes in practice
The workflow above is not dramatically different from what a careful statistician would do anyway. The discipline of using log_action() instead of a comment, and log_note() instead of an email, is modest. What changes is the output.
Instead of an audit trail that was assembled retrospectively, you have one that was produced contemporaneously, in the order events actually occurred, with cryptographic evidence that it has not been altered since. The difference matters at an FDA inspection, at an SMC meeting where the chair asks when a decision was made, and in the event of a litigation where the timeline of safety signal response is at issue.
Log before and after every consequential decision — not just at the end. The decisions that seem routine in isolation are exactly the decisions regulators ask about.
The full pkgdown article includes complete R code with simulated ADSL/ADAE datasets, all safety tables, AE grade distribution plots, and a rendered audit trail preview. The regulog package is available on GitHub.
Ndoh Penn is a biostatistician based in Antwerp, Belgium, and the author of regulog, lineager, reproducr, and bayprior. Questions — hello@reprostats.org.