--- title: "Dependency-Aware SAS-to-R Migration" author: "Songyue Chen" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Dependency-Aware SAS-to-R Migration} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Overview `sas2r` provides a dependency-aware migration framework that translates SAS programs and multi-module clinical pipelines into modular, reproducible R bundles. `sas2r` analyzes macro definitions, `%include` relationships, dataset lineages, and configured outputs to schedule programs in dependency order. Unresolved dependencies and unsupported source constructs remain visible in the report. ## Migration Pipeline Architecture A complete migration workflow follows six coordinated phases: 1. **Dependency analysis and scheduling**: Discovers module dependencies, macro definitions and data references. A deterministic coordinator assigns dependency-ready programs and called macros; cycles and unresolved dependencies remain findings. 2. **Translation and initial review**: Builds a deterministic baseline, hands unsupported source to the configured translator, checks the candidate and obtains independent review. Independent components can overlap in separate R processes when parallel mode is enabled. 3. **Immediate checks and repair**: Runs smoke execution and bounded source-grounded repairs one component at a time, including re-review and checks of affected consumers. Dependents wait until their providers finish this processing. 4. **Final review checkpoint**: Holds the selected code and helpers fixed, reuses reviews whose context still matches, and reviews remaining components. These reviews can overlap but do not repair code during the pass. 5. **Bundle execution and output checks**: Runs the complete generated bundle in dependency order inside an isolated subprocess using copy-on-write library registries. It checks required datasets and TLFs, compares configured dataset references, and runs bounded source-grounded repair followed by fresh execution. This phase remains serial. 6. **Authoritative reporting**: Produces machine-readable (`report/report.json`) and human-readable (`report/translation.md`) evidence, including unavailable checks and unresolved findings. The coordinator owns selected revisions, checkpoints and the shared usage budget. Each process uses the existing translator, reviewer and fixer context builders, tools and settings with a snapshot of the selected dependencies. Processes report results to the coordinator; they do not share a live conversation. In parallel mode, an unresolved dependency finding defers its component and known descendants while independent work continues. Required deferred execution or incomplete review prevents the run from becoming `migration_ready` or `validated`; inspect the reported status and reason for the affected components. ## Configuring a Clinical Study Migration (`_sas2r.yml`) Create a `_sas2r.yml` file in your clinical study directory: ```yaml project: cdsc_pilot_study libraries: sdtm: path: data/sdtm engine: xpt adam: path: data/adam engine: xpt write: rds outputs: datasets: - adam.adsl - adam.adae - adam.advs_bds # Golden SAS reference datasets for automated validation references: adam.adsl: data/reference/adsl.xpt adam.adae: data/reference/adae.sas7bdat adam.advs_bds: data/reference/advs.rds assertions: adam.adsl: keys: [STUDYID, USUBJID] numeric_tolerance: 1e-6 adam.advs_bds: keys: [STUDYID, USUBJID, PARAMCD, AVISITN, ATPTN] numeric_tolerance: 1e-6 llm: provider: gemini # or deepseek, openai, anthropic model: gemini-3.8-flash reasoning_effort: high max_output_tokens: 65536 timeout_seconds: 900 max_tries: 1 capabilities: tool_calling: native structured_output: fallback ``` `max_parallel_translations` counts concurrent program or macro workflows, including their reviews and repairs. It does not allocate one slot per agent role or per CPU. Establish a baseline at 1, then evaluate 2 against the same source, inputs and quality checks. Higher values depend on provider capacity and local memory; local execution and repair remain serial. Set it with the `sas_translate()` argument `max_parallel_translations`. Parallel mode is opt-in while live quality and performance validation remains outstanding. Gemini Flash and DeepSeek Flash are suggested starting points for evaluation; stronger frontier models remain available. Choose using source review and output evidence for the actual study. See the [provider settings guide](https://github.com/songyue-chen/sas2r/blob/main/docs/llm-providers.md) for provider-specific output limits, timeouts and capabilities. The adapter preserves native provider conversation history through tool calls. Offline replay tests cover tool continuation and parallel workers on ellmer 0.5.0; they do not establish live-model translation quality or study-level accuracy. ## Inspect the setup offline ```r library(sas2r) migration_dir <- tempfile("sas2r-migration-") check <- sas_preflight("programs/", # the folder of SAS programs to translate config = "_sas2r.yml", out_dir = migration_dir, diagnose = "off") print(check) check$libraries check$inputs check$unsupported stopifnot(check$model_calls == 0L) ``` This reads source files and directory listings without model calls, dataset reads, program execution, or output writes. The program folder is the `path` argument, not a configuration key; a single `.sas` file also works, and `recursive = TRUE` includes subfolders. Reuse `check$project` for translation while sources remain unchanged. `migration.execution_order` can name all executable root programs in their intended shared-session order; each WORK read then uses the latest preceding completed write. Included programs execute at their call sites. When the order is omitted, dependency inference remains the default. With `diagnose = "auto"`, configured AI can suggest corrections for findings in one bounded request; advice never overrides static checks. Use `qc_profile()` with `outputs$profiles` and a named `profile` in each target's assertions to require labels, formats, types/order, keys, uniqueness, row counts, and per-variable reference tolerances. These checks add study requirements; passing them alone does not establish SAS reference agreement. ## Declare a shared-session program order This complete offline example inspects three programs. The second program reads the first version of `work.tmp`, then replaces it. The third reads that replacement. ```r ordered_study <- tempfile("sas2r-ordered-study-") dir.create(ordered_study) writeLines("data work.tmp; value=1; run;", file.path(ordered_study, "create.sas")) writeLines("data work.tmp; set work.tmp; value=value+1; run;", file.path(ordered_study, "replace.sas")) writeLines("data work.final; set work.tmp; run;", file.path(ordered_study, "read.sas")) ordered_config <- list(migration = list( execution_order = c("create.sas", "replace.sas", "read.sas") )) ordered_check <- sas_preflight(ordered_study, config = ordered_config, diagnose = "off") ordered_check$pipeline$execution_order ordered_check$inputs[c("dataset", "status")] stopifnot(ordered_check$pipeline$order_source == "configured", all(ordered_check$inputs$status == "generated"), ordered_check$model_calls == 0L) unlink(ordered_study, recursive = TRUE) ``` For an R configuration list, these paths are relative to the scanned project directory. In YAML they are relative to the configuration file. List every root program exactly once; called macros, startup and includes keep their existing roles. The order does not change which files `path` and `recursive` select. Rescan the sources when changing an order on a previously scanned project. Explicit order applies to translation, smoke checks and final execution. Ordered roots translate sequentially, and smoke checks replay the preceding session. Unknown macro effects can still leave state deferred. A later writer does not make an earlier absent permanent input available. Omitting the order retains dependency inference. ## Running a Migration in R To migrate a single SAS script or an entire directory of SAS files, call `sas_translate()`. These examples write under the R session temporary directory; supply your own destination when you want to retain the files. ```r library(sas2r) # Load API keys from environment readRenviron("~/.Renviron") # Translate and execute the study migration result <- sas_translate( path = check$project, out_dir = migration_dir, max_parallel_translations = 1L, # programs or macros translated at once execute = TRUE, max_program_repair_rounds = 1L, max_bundle_repairs_per_component = 2L, max_bundle_repair_rounds = NULL # Optional limits, commented out by default. Uncomment to enforce them; the # run stops admitting requests when a limit is reached. Requests per program vary by model # and study; size ceilings from a completed run's usage summary. # Catalog costs are estimates; an in-flight request may exceed the threshold. # , budget_usd = 10, budget_mode = "soft" # , usage_limits = list(max_calls = 700, max_tool_calls = 700, max_wall_time = 14400) ) # Inspect the result object print(result) cat("Status:", result$status, "\n") cat("Bundle Directory:", result$bundle_dir, "\n") cat("Outputs Directory:", result$outputs_dir, "\n") cat("Report:", result$report_path, "\n") # View generated R code cat(sas_code(result)) # Export selected code, generated outputs, reports, and the dependency/input guide export_dir <- tempfile("sas2r-export-") sas_write(result, export_dir) ``` Outputs preserve library subdirectories: ADSL is at `file.path(result$outputs_dir, "datasets", "adam", "adsl.rds")`. The export contains a dependency-ordered `run.R`, `run-order.json`, and a README with run/edit/move instructions. Programs and macros are in `programs/` and `macros/`; saved automated outputs are under `saved-outputs/`, separate from manual writes to `output/`. From the export directory, use `Rscript run.R` or `source("run.R", chdir = TRUE)`. Install the R dependencies listed in its guide and update input paths in `autoexec.R` and explicit source LIBNAME statements when inputs move. Input datasets are external dependencies; the exported report describes the original selected execution and is not refreshed by a manual rerun. Repeat the same migration call with `resume = TRUE` to reuse saved revisions and completed reviews when source, inputs, configuration, runtime, and prompts still match. Changing only the parallel translation limit does not invalidate reuse. Missing or changed artifacts regenerate and unavailable reviews are retried. Passing or deferred smoke results can be reused when their full context matches; full-bundle execution and output checks use fresh attempts. Parallel checkpoints distinguish drafts from components that have completed immediate processing, and save final reviews individually. Repair and recorded revisit counts survive resume. The usage ledger remains cumulative across resumed runs. Every limit is commented out by default so a run finishes without interruption; uncomment `budget_usd` or a `usage_limits` ceiling only if you want the run to stop when it is reached; requests per program vary by model and study, so size a ceiling from a completed run's usage summary. Unknown-cost calls must not be read as zero spend. ### Four Canonical Bundle Statuses Each translation attempt resolves to one of four authoritative statuses: - **`validated`**: Execution, required output checks, and lineage requirements pass, with a passing reference comparison for at least one required target. Other outputs can remain unreferenced. - **`migration_ready`**: Execution, required output checks, and lineage requirements pass without passing reference evidence for a required target. - **`needs_review`**: Execution was deferred (`execute = FALSE`), or required review/lineage evidence is incomplete or blocked. Programs may still have executed successfully. - **`blocked`**: Bundle execution failed or required output checks failed, including missing/unreadable datasets and reference mismatches. Unavailable dependencies defer affected components while independent components can still execute. Inspect coverage as well as status. The reports separate produced, reference-compared, passed, and reference-passed outputs and list `validated_targets` and `unreferenced_targets`. A `validated` bundle is not a claim that every output was compared. ### Review completion and reference comparability `reviewer ...: ok` means the agent call completed. Its semantic verdict is in the review record; `coordinator ...: reviewed` can still be followed by repair. Mechanical checks and smoke tests can pass while the reviewer finds a logic issue. A new repaired revision is checked and reviewed again. Reference datasets must correspond to the supplied SAS source, input snapshot, formats, macros, and parameters. Different subject inclusion rules, KEEP lists, or analysis visit definitions can produce discrepancies even when the generated R follows the source. Trace differences to the source and inputs before treating them as translation errors. Independent R reconstruction is useful diagnostic evidence but is not an execution by SAS. ## Component Evidence Ladder Within each bundle, individual components begin without an evidence level and accumulate evidence bound to their revision: - `reviewed_only`: Evaluated by an independent reviewer without material issues. - `runtime_verified`: A reviewed revision has execution coverage from a smoke attempt or full bundle execution. - `output_verified`: The component's output requirements passed the migration gate. - `reference_validated`: Output lineage has passing reference-comparison evidence under configured tolerances. Smoke outcomes are recorded separately. A smoke pass with unavailable review does not earn a higher evidence level. Reference provenance is established separately from the comparison result. ## Attempt Isolation & Copy-on-Write The library helpers separate input reads from generated writes during translation: - The library registry reads original input members and directs generated writes to attempt-specific output paths. This is a helper-level convention, not a read-only filesystem mount or sandbox; arbitrary generated R can still access files outside the registry. - Write targets and scratch WORK libraries are isolated under `/diagnostics/bundle_attempts/bundle_attempt_001/`. Every run has its own timestamp-first folder. Open its `START_HERE.html` for reports and all available selected code in `bundle/`. Saved deliverables go to `outputs/`; requested WORK datasets count as deliverables, while unrequested scratch stays in diagnostics. Manual reruns write to `bundle/output/` and do not change the original evidence. - Multi-round repairs select improved attempts deterministically and prune rejected attempts according to configuration. ## Model Privacy & Clinical Data Protection `sas2r` keeps reference comparison answers out of translation and repair agents: - **Transmission Boundary**: Prompts carry SAS and R code, input schema metadata, helper interfaces and execution diagnostics. Reference comparison summaries, digests, values and reports stay in local reports and the public comparison API. Project tool overrides cannot restore comparison access for these agents. - **Agent evidence**: Both `code_only` and the legacy `bounded` policy exclude dataset row numbers, subject identifiers, key values, cell values, previews and raw runtime messages. Focused mismatch investigation receives approved aggregate patterns; fixers and candidate acceptance remain reference-blind. Source code and comments are not automatically de-identified. - **Human diagnosis**: The start page shows dataset dimensions, row alignment, shared column names, measured differences, repair decisions and numbered source/code links. AI explanations reuse current reviews or make one bounded request under the existing usage budget; `migration.report_diagnosis: off` disables them. Advice cannot change validation or feed repairs. A 100% row-alignment result does not mean equal values. - **Data Residency**: All dataset reading, execution, and comparison run in local R processes on your infrastructure; confirm your configured endpoint meets your organization's data residency obligations before enabling an LLM provider. ## Parity Disclaimer > **THIS IS NOT PARITY**: Automated translation by `sas2r` does not guarantee regulatory equivalence or replace independent statistical validation. All migrated R code and generated artifacts must be formally validated according to standard clinical operating procedures prior to regulatory submission.