跳到正文
cccskills
免费在 GitHub 公开

implement-audit-trail

Implement audit trail functionality for R projects in regulated environments. Covers logging, provenance tracking, electronic signatures, data integrity checks, and 21 CFR Part 11 compliance. Use when an R analysis requires electronic records compliance (21 CFR Part 11), when you need to track who did what and when in an analysis, when implementing data provenance tracking, or when creating tamper-evident analysis logs for regulatory submissions.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md10.4 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Implement Audit Trail

Add audit trail capabilities to R projects for regulatory compliance.

When to Use

  • R analysis requires electronic records compliance (21 CFR Part 11)
  • Need to track who did what, when, and why in an analysis
  • Implementing data provenance tracking
  • Creating tamper-evident analysis logs

Inputs

  • Required: R project with data processing or analysis scripts
  • Required: Regulatory requirements (which audit trail elements are mandatory)
  • Optional: Existing logging infrastructure
  • Optional: Electronic signature requirements

Procedure

Step 1: Set Up Structured Logging

Create R/audit_log.R:

#' Initialize audit log for a session
#'
#' @param log_dir Directory for audit log files
#' @param analyst Name of the analyst
#' @return Path to the created log file
init_audit_log <- function(log_dir = "audit_logs", analyst = Sys.info()["user"]) {
  dir.create(log_dir, showWarnings = FALSE, recursive = TRUE)

  log_file <- file.path(log_dir, sprintf(
    "audit_%s_%s.jsonl",
    format(Sys.time(), "%Y%m%d_%H%M%S"),
    analyst
  ))

  entry <- list(
    timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z"),
    event = "SESSION_START",
    analyst = analyst,
    r_version = R.version.string,
    platform = .Platform$OS.type,
    working_directory = getwd(),
    session_id = paste0(Sys.getpid(), "-", format(Sys.time(), "%Y%m%d%H%M%S"))
  )

  write(jsonlite::toJSON(entry, auto_unbox = TRUE), log_file, append = TRUE)
  options(audit_log_file = log_file, audit_session_id = entry$session_id)

  log_file
}

#' Log an audit event
#'
#' @param event Event type (DATA_IMPORT, TRANSFORM, ANALYSIS, EXPORT, etc.)
#' @param description Human-readable description
#' @param details Named list of additional details
log_audit_event <- function(event, description, details = list()) {
  log_file <- getOption("audit_log_file")
  if (is.null(log_file)) stop("Audit log not initialized. Call init_audit_log() first.")

  entry <- list(
    timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z"),
    event = event,
    description = description,
    session_id = getOption("audit_session_id"),
    details = details
  )

  write(jsonlite::toJSON(entry, auto_unbox = TRUE), log_file, append = TRUE)
}

Expected: R/audit_log.R created with init_audit_log() and log_audit_event() functions. Calling init_audit_log() creates the audit_logs/ directory and a timestamped JSONL file. Each log entry is a single JSON line with timestamp, event, analyst, and session_id fields.

On failure: If jsonlite::toJSON() fails, ensure the jsonlite package is installed. If the log directory cannot be created, check file system permissions. If timestamps lack timezone, verify %z is supported on the platform.

Step 2: Add Data Integrity Checks

#' Compute and log data hash for integrity verification
#'
#' @param data Data frame to hash
#' @param label Descriptive label for the dataset
#' @return SHA-256 hash string
hash_data <- function(data, label = "dataset") {
  hash_value <- digest::digest(data, algo = "sha256")

  log_audit_event("DATA_HASH", sprintf("Hash computed for %s", label), list(
    hash_algorithm = "sha256",
    hash_value = hash_value,
    nrow = nrow(data),
    ncol = ncol(data),
    columns = names(data)
  ))

  hash_value
}

#' Verify data integrity against a recorded hash
#'
#' @param data Data frame to verify
#' @param expected_hash Previously recorded hash
#' @return Logical indicating whether data matches
verify_data_integrity <- function(data, expected_hash) {
  current_hash <- digest::digest(data, algo = "sha256")
  match <- identical(current_hash, expected_hash)

  log_audit_event("DATA_VERIFY",
    sprintf("Data integrity check: %s", ifelse(match, "PASS", "FAIL")),
    list(expected = expected_hash, actual = current_hash))

  if (!match) warning("Data integrity check FAILED")
  match
}

Expected: hash_data() returns a SHA-256 hash string and logs a DATA_HASH event. verify_data_integrity() compares current data against a stored hash and logs a DATA_VERIFY event with PASS or FAIL status.

On failure: If digest::digest() is not found, install the digest package. If hashes don't match for identical data, check that column order and data types are consistent between hashing and verification.

Step 3: Track Data Transformations

#' Wrap a data transformation with audit logging
#'
#' @param data Input data frame
#' @param transform_fn Function to apply
#' @param description Description of the transformation
#' @return Transformed data frame
audited_transform <- function(data, transform_fn, description) {
  input_hash <- digest::digest(data, algo = "sha256")
  input_dim <- dim(data)

  result <- transform_fn(data)

  output_hash <- digest::digest(result, algo = "sha256")
  output_dim <- dim(result)

  log_audit_event("DATA_TRANSFORM", description, list(
    input_hash = input_hash,
    input_rows = input_dim[1],
    input_cols = input_dim[2],
    output_hash = output_hash,
    output_rows = output_dim[1],
    output_cols = output_dim[2]
  ))

  result
}

Expected: audited_transform() wraps any transformation function, logging input dimensions and hash, output dimensions and hash, and the transformation description as a DATA_TRANSFORM event.

On failure: If the transform function errors, the audit event is not logged. Wrap the transform in tryCatch() to log both successes and failures. Ensure the transform function accepts and returns a data frame.

Step 4: Log Session Environment

#' Log complete session information for reproducibility
log_session_info <- function() {
  si <- sessionInfo()

  log_audit_event("SESSION_INFO", "Complete session environment recorded", list(
    r_version = si$R.version$version.string,
    platform = si$platform,
    locale = Sys.getlocale(),
    base_packages = si$basePkgs,
    attached_packages = sapply(si$otherPkgs, function(p) paste(p$Package, p$Version)),
    renv_lockfile_hash = if (file.exists("renv.lock")) {
      digest::digest(file = "renv.lock", algo = "sha256")
    } else NA
  ))
}

Expected: A SESSION_INFO event logged with R version, platform, locale, attached packages with versions, and the renv lockfile hash (if applicable).

On failure: If sessionInfo() returns incomplete package information, ensure all packages are loaded via library() before calling log_session_info(). The renv lockfile hash will be NA if the project does not use renv.

Step 5: Implement in Analysis Scripts

# 01_analysis.R
library(jsonlite)
library(digest)

# Start audit trail
log_file <- init_audit_log(analyst = "Philipp Thoss")

# Import data with audit
raw_data <- read.csv("data/raw/study_data.csv")
raw_hash <- hash_data(raw_data, "raw study data")

# Transform with audit
clean_data <- audited_transform(raw_data, function(d) {
  d |>
    dplyr::filter(!is.na(primary_endpoint)) |>
    dplyr::mutate(bmi = weight / (height/100)^2)
}, "Remove missing endpoints, calculate BMI")

# Run analysis
log_audit_event("ANALYSIS_START", "Primary efficacy analysis")
model <- lm(primary_endpoint ~ treatment + age + sex, data = clean_data)
log_audit_event("ANALYSIS_COMPLETE", "Primary efficacy analysis", list(
  model_class = class(model),
  formula = deparse(formula(model)),
  n_observations = nobs(model)
))

# Log session
log_session_info()

Expected: Analysis scripts initialize the audit log at the start, log each data import, transformation, and analysis step, and record session info at the end. The JSONL log file captures the complete provenance chain.

On failure: If init_audit_log() is missing, ensure R/audit_log.R is sourced or the package is loaded. If events are missing from the log, verify that log_audit_event() is called after every significant operation.

Step 6: Git-Based Change Control

Complement the application-level audit trail with git:

# Use signed commits for non-repudiation
git config commit.gpgsign true

# Descriptive commit messages referencing change control
git commit -m "CHG-042: Add BMI calculation to data processing

Per change request CHG-042, approved by [Name] on [Date].
Validation impact assessment: Low risk - additional derived variable."

Expected: Git commits are signed (GPG) and use descriptive messages referencing change control IDs. The combination of application-level JSONL audit trail and git history provides a complete change control record.

On failure: If GPG signing fails, configure the signing key with git config --global user.signingkey KEY_ID. If the key is not set up, follow gpg --gen-key to create one.

Validation

  • Audit log captures all required events (start, data access, transforms, analysis, export)
  • Timestamps use ISO 8601 format with timezone
  • Data hashes enable integrity verification
  • Session information is recorded
  • Logs are append-only (no deletion or modification)
  • Analyst identity is captured for each session
  • Log format is machine-readable (JSONL)

Common Pitfalls

  • Logging too much: Focus on regulated events. Don't log every variable assignment.
  • Mutable logs: Audit logs must be append-only. Use JSONL (one JSON object per line).
  • Missing timestamps: Every event needs a timestamp with timezone.
  • No session context: Each log entry should reference the session for correlation.
  • Forgetting to initialize: Scripts must call init_audit_log() before any analysis.

Related Skills

  • setup-gxp-r-project - project structure for validated environments
  • write-validation-documentation - validation protocols and reports
  • validate-statistical-output - output verification methodology
  • configure-git-repository - version control as part of change control

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Execute structural metamorphosis using strangler fig migration, chrysalis phases, and interface preservation. Covers transformation planning, parallel running, progressive cutover, rollback design, and post-metamorphosis stabilization for system architecture evolution. Use when assess-form has classified the system as READY for transformation, when migrating from monolith to microservices, when replacing a core subsystem while dependents continue operating, or when any architectural change must be gradual rather than big-bang.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

adaptic

免费

Master skill composing the 5-step synoptic cycle for panoramic synthesis across multiple domains. Orchestrates meditate, expand-awareness, observe, awareness, integrate-gestalt, and express-insight into a coherent process that produces unified understanding rather than sequential compromise. Use when a problem genuinely spans 3+ domains and the interactions between domains matter more than depth in any one, when sequential analysis feels like compromise rather than integration, or before major architectural decisions affecting multiple stakeholders.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

Scaffold a new puzzle type across all 10+ pipeline integration points in jigsawR. Creates the core puzzle module, wires it into the unified pipeline (generation, positioning, rendering, adjacency), adds ggpuzzle geom/stat layers, updates DESCRIPTION and config.yml, extends the Shiny app, and creates a comprehensive test suite. Use when adding a completely new puzzle type to the package or following the 10-point integration checklist to ensure nothing is missed end-to-end.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

Add Rcpp or RcppArmadillo integration to an R package for high-performance C++ code. Covers setup, writing C++ functions, RcppExports generation, testing compiled code, and debugging. Use when an R function is too slow and profiling confirms a bottleneck, when you need to interface with existing C/C++ libraries, or when implementing algorithms (loops, recursion, linear algebra) that benefit from compiled code.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

aikido

免费

Practice aikido as a defensive martial art emphasizing harmony, redirection, and controlled resolution. Covers centering and grounding, ukemi (safe falling and rolling), core techniques (irimi-nage, shiho-nage, kote-gaeshi, ikkyo), blending and tai sabaki (body movement), randori (multiple attacker practice), weapons awareness, and applying principles off the mat. Use when learning a defensive martial art that prioritizes de-escalation, developing the ability to redirect aggression without causing unnecessary harm, building safe falling skills, or cultivating calm centeredness under physical pressure.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

Analyze an arbitrary codebase to identify functions, APIs, and data sources suitable for exposure as MCP tools, producing a tool specification document. Use when planning an MCP server for an existing project, auditing a codebase before wrapping it as an AI-accessible tool surface, comparing what a codebase can do versus what is already exposed via MCP, or generating a tool spec to hand off to scaffold-mcp-server.

中文简介正在准备中。当前显示原文说明。

pjt222/agent-almanac422026年10月15日 更新

查看 pjt222 的全部技能

报告此技能的问题