diff --git a/.cursor/README.md b/.cursor/README.md new file mode 100644 index 00000000..5d6ffdbe --- /dev/null +++ b/.cursor/README.md @@ -0,0 +1,36 @@ +# Cloud Agent environment + +This directory configures the Cursor Cloud Agent development environment for +`aFIPC`. + +- `environment.json` runs `install.sh` after the repository is checked out. +- `install.sh` provisions CRAN-release R plus the CRAN dependencies used by + documented verification (`mirt`, `testthat`, `roxygen2`, `rcmdcheck`) from + [r2u](https://eddelbuettel.github.io/r2u/) prebuilt binaries. It is + revision-agnostic: it never `R CMD INSTALL`s the current tree, because + environment builds snapshot `install` and do not rerun it on later checkouts. + +Reproducibility across agents is pinned by the environment-build snapshot; the +apt block in `install.sh` only reprovisions a bare image. + +Run the documented checks against the checkout: + +```bash +R_PROFILE_USER=/dev/null Rscript -e 'testthat::test_local()' +R_PROFILE_USER=/dev/null Rscript -e 'rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"))' +``` + +Run a single test file with the `filter` argument (matches +`tests/testthat/test-.R`): + +```bash +R_PROFILE_USER=/dev/null Rscript -e 'testthat::test_local(filter = "surveyFA")' +``` + +See `ARCHITECTURE.md` (sections 1 and 8) for how this fits the repository. + +## Security + +`install.sh` stores each repository key in its own keyring under +`/etc/apt/keyrings` and binds it to that repository with `signed-by`, so a key +can only vouch for its own source (no global `trusted.gpg.d` trust). diff --git a/.cursor/install.sh b/.cursor/install.sh index 4f41758b..85479118 100755 --- a/.cursor/install.sh +++ b/.cursor/install.sh @@ -30,15 +30,19 @@ if ! toolchain_ready; then sudo apt-get update -qq sudo apt-get install -y --no-install-recommends wget ca-certificates gnupg dirmngr + # Store each repository key in its own keyring and bind it to that repo with + # signed-by, so a key can only vouch for its own source (no global trust). + sudo install -d -m 0755 /etc/apt/keyrings + wget -q -O- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \ - | sudo tee /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc >/dev/null - echo "deb [arch=${ARCH}] https://cloud.r-project.org/bin/linux/ubuntu ${CRAN_SUITE}/" \ - | sudo tee /etc/apt/sources.list.d/cran_r.list + | sudo gpg --dearmor -o /etc/apt/keyrings/cran_r.gpg + echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/cran_r.gpg] https://cloud.r-project.org/bin/linux/ubuntu ${CRAN_SUITE}/" \ + | sudo tee /etc/apt/sources.list.d/cran_r.list >/dev/null wget -q -O- https://eddelbuettel.github.io/r2u/assets/dirk_eddelbuettel_key.asc \ - | sudo tee /etc/apt/trusted.gpg.d/cranapt_key.asc >/dev/null - echo "deb [arch=${ARCH}] https://r2u.stat.illinois.edu/ubuntu ${VERSION_CODENAME} main" \ - | sudo tee /etc/apt/sources.list.d/cranapt.list + | sudo gpg --dearmor -o /etc/apt/keyrings/cranapt.gpg + echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/cranapt.gpg] https://r2u.stat.illinois.edu/ubuntu ${VERSION_CODENAME} main" \ + | sudo tee /etc/apt/sources.list.d/cranapt.list >/dev/null printf 'Package: *\nPin: release o=CRAN-Apt Project\nPin: release l=CRAN-Apt Packages\nPin-Priority: 700\n' \ | sudo tee /etc/apt/preferences.d/99cranapt >/dev/null @@ -47,4 +51,12 @@ if ! toolchain_ready; then r-base-core r-cran-mirt r-cran-testthat r-cran-roxygen2 r-cran-rcmdcheck pandoc fi -echo "aFIPC toolchain ready: R=$(R --version | head -1)" +# Report resolved versions. Reproducibility across agents comes from the +# environment-build snapshot, which pins this toolchain at build time; this +# block only reprovisions on a bare image. +R_PROFILE_USER=/dev/null Rscript -e ' + cat(sprintf("aFIPC toolchain ready: R %s\n", getRversion())) + for (p in c("mirt", "testthat", "roxygen2", "rcmdcheck")) { + cat(sprintf(" %-10s %s\n", p, as.character(packageVersion(p)))) + } +'