## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(regulog) ## ----genesis------------------------------------------------------------------ log <- regulog_init(app = "demo", version = "1.0", user = "analyst") cat("Genesis hash:", log$genesis_hash, "\n") cat("Last hash: ", log$last_hash, "\n") ## ----first-entry-------------------------------------------------------------- log_action( log, "approved", "dataset_v1", "All quality checks passed — dataset approved for analysis" ) entry <- log$entries[[1L]] cat("Entry ID: ", entry$entry_id, "\n") cat("Prev hash: ", entry$prev_hash, "\n") # = genesis hash cat("Entry hash: ", entry$entry_hash, "\n") ## ----chain-------------------------------------------------------------------- log_action(log, "model_fit", "ANCOVA_v1", "Primary ANCOVA fitted per SAP") log_note(log, "Outlier in subject 042 retained per SAP section 8.3") cat("Entry 1 hash:", log$entries[[1L]]$entry_hash, "\n") cat("Entry 2 prev:", log$entries[[2L]]$prev_hash, "\n") cat("Match: ", log$entries[[1L]]$entry_hash == log$entries[[2L]]$prev_hash, "\n") ## ----verify-intact------------------------------------------------------------ verify_log(log) ## ----verify-result------------------------------------------------------------ result <- verify_log(log, verbose = FALSE) str(result) ## ----tamper-content----------------------------------------------------------- # Simulate a reason being altered original_reason <- log$entries[[1L]]$reason log$entries[[1L]]$reason <- "ALTERED" result <- suppressWarnings(verify_log(log, verbose = FALSE)) cat("Intact: ", result$intact, "\n") cat("First broken: ", result$first_broken, "\n") cat("Error: ", result$errors[[1L]], "\n") log$entries[[1L]]$reason <- original_reason # restore ## ----tamper-delete------------------------------------------------------------ saved <- log$entries log$entries <- log$entries[-2L] # remove entry 2 result <- suppressWarnings(verify_log(log, verbose = FALSE)) cat("Intact: ", result$intact, "\n") cat("First broken: ", result$first_broken, "\n") log$entries <- saved # restore ## ----tamper-hash-------------------------------------------------------------- saved_prev <- log$entries[[2L]]$prev_hash log$entries[[2L]]$prev_hash <- paste(rep("0", 64L), collapse = "") result <- suppressWarnings(verify_log(log, verbose = FALSE)) cat("Intact: ", result$intact, "\n") cat("First broken: ", result$first_broken, "\n") log$entries[[2L]]$prev_hash <- saved_prev # restore verify_log(log, verbose = FALSE)$intact # confirm restored ## ----verify-file, eval = FALSE------------------------------------------------ # # Can be run by a QC reviewer with no knowledge of the original session # result <- verify_log("logs/trial001_audit.rlog") # # # Structured result for programmatic use # if (!result$intact) { # warning(sprintf( # "Log integrity failure: %d error(s). First broken entry: #%d", # length(result$errors), result$first_broken # )) # } ## ----persistent, eval = FALSE------------------------------------------------- # log <- regulog_init( # app = "trial-analysis", # version = "1.0.0", # user = "jsmith", # path = "logs/trial001_audit.rlog" # ) # # log_action(log, "data_read", "adsl.sas7bdat", "Reading ADSL") # # ↑ This line is written to disk immediately # # # The .rlog file at this point: # # Line 1: {"entry_id":0,"type":"GENESIS",...} # # Line 2: {"entry_id":1,"type":"ACTION","action":"data_read",...} ## ----archival, eval = FALSE--------------------------------------------------- # # Signed CSV — chain_intact and verified_at stamped on every row # export_audit_trail(log, # format = "csv", # signed = TRUE, # path = "archive/trial001_audit_2026-06-23.csv" # ) # # # Original .rlog — keep this too; it allows re-verification later # file.copy( # "logs/trial001_audit.rlog", # "archive/trial001_audit_2026-06-23.rlog" # )