From 31285066a7004c2b24fac570c2070d4c09bd7b8b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 17:03:01 +0000 Subject: [PATCH 1/4] ci: harden Cloud Agent apt keyrings and document .cursor Follow-up to #258 (merged): - Store CRAN and r2u keys under /etc/apt/keyrings and bind each repo with signed-by, replacing global /etc/apt/trusted.gpg.d trust (CodeRabbit). - Print resolved R + mirt/testthat/roxygen2/rcmdcheck versions after provisioning; cross-agent reproducibility is pinned by the environment build snapshot (this block only reprovisions a bare image). - Add .cursor/README.md documenting the environment. Validated: shellcheck clean; signed-by repos trusted by apt update; r-cran-mirt resolves from r2u. Co-authored-by: Seongho Bae --- .cursor/README.md | 23 +++++++++++++++++++++++ .cursor/install.sh | 26 +++++++++++++++++++------- 2 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 .cursor/README.md diff --git a/.cursor/README.md b/.cursor/README.md new file mode 100644 index 00000000..92310fb5 --- /dev/null +++ b/.cursor/README.md @@ -0,0 +1,23 @@ +# 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"))' +``` + +See `ARCHITECTURE.md` (sections 1 and 8) for how this fits the repository. 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)))) + } +' From be0e22a610ad811ac5b72d8a13bd7067ee9445a4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:26:30 +0900 Subject: [PATCH 2/4] docs(cursor): note signed-by keyring security posture Document the per-repo signed-by APT keyrings added in this PR (and refresh the head so the transient-503 Strix check re-runs on a healthy API). --- .cursor/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.cursor/README.md b/.cursor/README.md index 92310fb5..e0104e1b 100644 --- a/.cursor/README.md +++ b/.cursor/README.md @@ -21,3 +21,9 @@ R_PROFILE_USER=/dev/null Rscript -e 'rcmdcheck::rcmdcheck(args = c("--no-manual" ``` 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). From 952bfd501c8e24e90acf5b53e3efb9173d655915 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 23:14:19 +0000 Subject: [PATCH 3/4] ci: re-run checks after transient GitHub 503 incident The prior noema-review/strix failures were transient: their internal gh API calls returned HTTP 503 during a GitHub platform incident (repo is public; the same calls succeed now). No code change is needed; this empty commit re-runs the required review workflows on a healthy API. Co-authored-by: Seongho Bae From 6e758659d96a3d8c501c4c8f6901f82618dbe542 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 08:15:43 +0900 Subject: [PATCH 4/4] docs(cursor): document single-test-file runs; re-run checks Adds a filter example for testthat::test_local(). Also re-runs the required review workflows via a user-token commit; the prior noema-review/strix reds were transient GitHub HTTP 503s inside those workflows' gh calls, and an installation-token push does not re-trigger pull_request_target workflows. --- .cursor/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.cursor/README.md b/.cursor/README.md index e0104e1b..5d6ffdbe 100644 --- a/.cursor/README.md +++ b/.cursor/README.md @@ -20,6 +20,13 @@ 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