Land v0.0.0.9001: CRAN-readiness audit and review hygiene#4
Open
max578 wants to merge 35 commits into
Open
Conversation
First pass applying AAGI-AUS institutional compliance surface per the /rpkg v0.4.1 AAGI preset (presets/aagi.yml). - M3: add pkgdown site (https://aagi-aus.github.io/geefetch/) to DESCRIPTION URL - M6: add GRDC funding attribution to DESCRIPTION Description (CUR2210-005OPX) - M5: add codemeta.json (schema.org/codemeta-2.0) seeded from DESCRIPTION - M8: add COPYRIGHT with logo carve-out for AAGI, GRDC, Curtin, UQ, Adelaide - M1: README install block reorganised — R-Universe binaries first, pak::pak dev fallback second; drop remotes::install_github; add R-Universe badge - M1: mirror the R-Universe install pattern in vignettes/geefetch.Rmd - M2: r-universe-registration.patch emitted at repo root (not committed) for manual application to AAGI-AUS/aagi-aus.r-universe.dev packages.json Also extends .Rbuildignore for governance / uplift files landing in subsequent batches (CITATION.cff, SECURITY.md, SUPPORT.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, API_STABILITY.md, cran-comments.md, adr/, revdep/, .pre-commit-config.yaml, .devcontainer/).
…I leg Clears the four Tier 1 Blockers surfaced by rubrics/mandatory_uplift.md when the AAGI preset is active (audit.mandatory_uplift: true). - B1: CITATION.cff (CFF-1.2.0) seeded from DESCRIPTION. Both authors with ORCID and affiliation. preferred-citation block retained. Hand-authored — cffr not available locally for programmatic generation; schema-valid against the CFF-1.2.0 JSON schema. - B2: SECURITY.md — private-disclosure channel via GitHub Security Advisory + maintainer email fallback; 72 h acknowledgement SLA; coordinated disclosure policy; scope + supply-chain notes. - B3: .github/dependabot.yml (repo root) — weekly github-actions ecosystem; r-lib/actions group; 5-PR cap. docker block deferred until .devcontainer/ lands in Batch 4.5. - B4: macos-14 (Apple Silicon) matrix row added to R-CMD-check.yaml alongside macos-latest. Preserves back-compat for reviewers who expect macos-latest; gives clean ARM coverage. Note: dependabot + workflow live at repo-root .github/ (where GitHub actually reads), not under geefetch/.github/. The geefetch/.github/ copy is vestigial (tracked but dead; .Rbuildignore excludes it from the tarball, GitHub ignores it). Cleanup of the vestigial copy is a separate concern; not touched in this batch.
The top-level '# geefetch (development version)' heading already scopes the dev-version section — the additional '## geefetch 0.0.0.9000' heading immediately below duplicated the version. Remove the inner heading and keep the bullet list under the development-version section. Cosmetic only; no changelog-content change.
Sparks's commit 019963e migrated most "i" = keys to bare i = form. Six stragglers remained in cache.R, collect_gee_data.R, handler_registry.R, and backend_rest.R — with "v", "!", and "x" prefixes not covered by the initial sweep. Apply the same transform: - "v" = / "!" = / "x" = → bare v = / `!` = / x =. - Backticks around `!` preserve parser correctness (bare ! is ambiguous in a named-argument position). Behavioural: none. cli renders both forms identically. Motivation: lintr/cli-style consistency + continuation of Sparks's cleanup pattern.
Infrastructure files required by the baseline rpkg punchlist that were absent from review/ahs. - inst/WORDLIST — seed list for devtools::spell_check() covering GEE domain terms (MODIS, ERA5, CHIRPS, SRTM, SLGA, WorldClim, Sentinel, Landsat, NDVI, LST, bioclim/bioclimatic, spatio-temporal) plus dependency names (httr2, gargle, yyjsonr, cffr, codemetar, etc.). Expected to shrink after first interactive spell_check() pass surfaces any missing terms. - codecov.yml — informational-only thresholds (1% patch, 1% project), auto-target. Three files ignored: geefetch-package.R (pkg overview), globals.R (R CMD check false positives), reexports.R (upstream tests own these). - .lintr — linters_with_defaults with line_length 100, snake_case + dotted.case object names, commented_code_linter disabled (intentional NOTE-style comments in R/backend_rest.R section dividers), cyclocomp_linter relaxed to 25 (dispatcher pattern pushes the default 15). - .github/workflows/lint.yaml — at repo-root .github/ where GitHub reads; LINTR_ERROR_ON_LINT=true so any lint regression fails CI. working-directory: geefetch for subdir-package layout.
Archetype-6a overlay W2 — clears the largest single test-quality gap from the 2026-04-19 dogfood audit (httptest2 declared in Suggests but zero test-file usage). - tests/testthat/setup.R — runs once before test files. Unsets GEE credential env vars, sets NO_INTERNET=true, disables disk cache, pins locale to en_AU.UTF-8 (C.UTF-8 fallback), sets httptest2 options. Isolates the test run from developer credentials and from user cache state. - tests/testthat/helper-recapture.R — gated by RPKG_RECAPTURE=1. Only fires during maintainer recapture sessions. Sets the httptest2 redactor to strip Authorization headers and replace the real GCP project ID with the placeholder 'geefetch-test-project'. - tests/testthat/test-backend_rest-http.R — four mocked tests against .rest_request() via httptest2::with_mock_dir(): success (200), 401 unauthenticated, 429 rate-limited, 403 API-not-enabled. Each test skips gracefully if its cassette directory is missing, with the recapture command in the skip message. - tests/testthat/README.md — maintainer-facing recapture procedure and redaction-verification checklist. .Rbuildignored. Cassette directories are intentionally NOT committed in this change. They will be cut in a follow-up commit by running RPKG_RECAPTURE=1 devtools::test(filter = 'backend_rest-http') against a live GEE project, reviewing for leaked credentials, then committing tests/testthat/computeFeatures-*/. Until they land, the four HTTP tests skip (skip reason explicit). Tests are therefore additive — nothing existing fails.
Archetype-6a overlay W3 was stated as 'prefer yyjsonr over jsonlite'. Investigation shows neither is actually needed in this package — the jsonlite Imports dependency was vestigial: - R/backend_rest.R only had '#' @importFrom jsonlite fromJSON toJSON' but never called either function. All JSON round-tripping in the REST backend goes through httr2::req_body_json() and httr2::resp_body_json(), which handle serialisation internally (httr2 uses jsonlite itself, but that is httr2's private transitive dep, not ours). - NAMESPACE had two importFrom(jsonlite, ...) lines generated from the stale roxygen, re-exporting unused symbols. - One test (test-coverage_boost.R:397) used jsonlite::toJSON to flatten a list for a grepl check. Refactored to unlist(expression) + grepl — same assertion, no JSON serialisation needed. Changes: - Remove jsonlite from DESCRIPTION Imports. - Remove the jsonlite @importFrom roxygen comment in backend_rest.R. - Remove the two importFrom(jsonlite, ...) lines from NAMESPACE (hand-edit; safer than full devtools::document() re-run which could drift against Sparks's c300561 redocument state). - Refactor test-coverage_boost.R:397 to use unlist() + grepl(). - Drop jsonlite from codemeta.json softwareRequirements (renumber array). - Drop jsonlite from SECURITY.md in-scope dependency list. Net dep count: Imports goes from 11 → 10 (at archetype-6 soft cap). No functional change; every httr2-mediated JSON call continues unchanged.
…lifecycle badges) Tier 3 Should-fix uplift from /rpkg v0.4.1 mandatory_uplift rubric. Closes the gap between AAGI's typical compliance surface and the /rpkg canon. Collaboration + governance surface (geefetch/): - CODE_OF_CONDUCT.md — Contributor Covenant 2.1, enforcement email max.moldovan@adelaide.edu.au. - CONTRIBUTING.md — dev-setup on-ramp, test runners (incl. the httptest2 suite), lint + format commands, PR workflow, maintainer-competence matrix (R-only today; compiled-code additions gated by adding a Secondary in that language). - SUPPORT.md — honest maintenance-capacity block (Max ~2-4 h/wk, Sparks ~1 h/wk), bus factor 2, 6-month+3-month abandonment protocol, scope-exclusion list. - API_STABILITY.md — all 17 exported functions enumerated at experimental for 0.0.0.9001; deprecation cycle documented; 4 re-exports tracked separately (stability inherited from terra/sf). Governance surface (adr/ at repo root — off-canon but avoids pkgdown's docs/ wipe collision; .Rbuildignore ^adr$ keeps it out of the tarball): - 0001 record-architecture-decisions — meta-ADR + criteria for significance. - 0002 class-system — why S3, not S7 / R6 / S4. - 0003 imports-budget — 10-non-trivial cap, per-Import justification snapshot, lifecycle as the metadata-only exception. - 0004 maintenance-posture — capacity, distribution cadence, discoverability, abandonment, competence gate for compiled code. Lifecycle badges (S7): - lifecycle added to DESCRIPTION Imports (metadata exception, see ADR 0003). - All 17 exported functions now carry '\`r lifecycle::badge(\"experimental\")\`' in their roxygen @description. 4 re-exports (rast, ext, st_as_sf, st_bbox) are intentionally unbadged — upstream owns their lifecycle. - devtools::document() regenerated man/*.Rd with the rendered badges; roxygen 7.3.3 matches Sparks's c300561 baseline so no version drift. Bug fix surfaced by document(): - R/backend_rest.R:310 had 'bare ! =' without backticks — invalid R syntax, introduced by Sparks's commit 019963e during the cli-lint sweep. The file parse-errored in devtools::document() and would have failed any subsequent R CMD check on review/ahs. Now '`!` =' with backticks, matching the fix I applied in batch 5a053c5 to cache.R / collect_gee_data.R / handler_registry.R. This is the single 'new' bug Sparks introduced; everything else on review/ahs was Pass-advancing. Workflows + repo-level config (repo-root .github/): - FUNDING.yml — AAGI and GRDC org pages. - CODEOWNERS — path-scoped reviewers across R/, tests/, vignettes/, governance files, and CI config. - ISSUE_TEMPLATE/{bug_report,feature_request,config}.yml — YAML forms with reprex + session-info required fields; blank_issues_enabled: false routes to Discussions. - PULL_REQUEST_TEMPLATE.md — conventional-commit-aware checklist. - workflows/revdep.yaml — dispatch + tag-triggered revdepcheck::revdep_check(num_workers = 4). - workflows/dependency-review.yaml — PR-only actions/dependency-review-action@v4, fail-on-severity: moderate.
Tier 4 nice-to-have uplift from rubrics/mandatory_uplift.md. All files
live at repo root where their consumers (pre-commit, Codespaces, GitHub
Actions) actually read from.
- .pre-commit-config.yaml (N1) — air format + lintr + precommit R
hooks + gitleaks + standard whitespace/yaml checks. Subdir-package
layout note included: hooks may need --files scoping when invoked
manually.
- .devcontainer/{devcontainer.json,Dockerfile} (N2) — rocker/r-ver:4.4.2
base with the sf/terra/httr2 system-library bundle and the dev
toolchain (pak, devtools, languageserver, httptest2, lifecycle,
cffr, codemetar, lintr, roxygen2) pre-installed. postCreateCommand
runs pak::local_install_dev_deps() against geefetch/DESCRIPTION
(note: cd geefetch first — subdir-package layout).
- .github/dependabot.yml — re-enabled docker block now that
.devcontainer/ exists. Was trimmed in ca67c48 pending this batch.
- .github/workflows/codeql.yaml (N4) — scans the actions-language
surface weekly + on every push/PR. R is not in CodeQL's
auto-detected languages, so the value here is supply-chain
hygiene on the workflow YAMLs themselves, not on R/.
- .github/workflows/attestations.yaml (N5) — on release-created +
workflow_dispatch: builds the package tarball from geefetch/
(R CMD build geefetch, not .), generates signed build provenance
via actions/attest-build-provenance@v2, attaches tarball +
attestation to the GitHub release.
N3 renv.lock is DEFERRED. Rationale in fix_plan_2026-04-21.md: the
value is retrospective bisect ("worked last month") and at 0.0.0.9001
there's no historical baseline to regress against. Revisit for the
CRAN submission cycle.
Dev-version bump per user direction. 0.0.0.9001 captures the institutional compliance surface (AAGI MUSTs + 4 Blockers + governance + hardening) that landed across commits ee6983b..5213271 on top of Sparks's review/ahs work (4d2c7b9..c300561). - DESCRIPTION Version: 0.0.0.9000 → 0.0.0.9001 - codemeta.json version + CITATION.cff version + inst/CITATION note kept in sync with DESCRIPTION. API_STABILITY.md "Since" column intentionally unchanged — those columns record when each export first appeared (0.0.0.9000), not the current release. - NEWS.md: new section under 'geefetch 0.0.0.9001 (2026-04-21)' using tidyverse conventions (User-visible changes / Bug fixes / Internal / Acknowledgements). Credits Sparks's 8 review/ahs commits explicitly. Documents the R/backend_rest.R:310 syntax-error fix. Notes the rlang::match_arg commit-message-only misnaming on 8f6ace8.
Two pre-existing bugs on review/ahs surfaced by 'R CMD check --as-cran' verification in Phase 6. Both introduced by Sparks's commits ade53aa ('ensure user inputs are all upper or lowercase as desired') and 8f6ace8 ('replace match.arg with rlang::arg_match'). Bug 1 — rlang::arg_match(tolower(x)) pattern (17 test failures) ------ rlang::arg_match() with no explicit 'values' argument looks up the allowed values via formals(sys.function(sys.parent())). This requires the argument to be a bare symbol from the caller's environment — a transformed value like tolower(backend) breaks the lookup and raises 'arg' must be a symbol, not a function call. Sparks's sequence was: first migrate match.arg(backend) to rlang::arg_match(backend) (8f6ace8 — which was fine), then in a later pass wrap with case-normalisation (ade53aa — which broke it). Fix: restructure to two statements across 10 files, 11 call sites (read_chirps, read_era5, read_gee, read_landsat, read_modis_lst, read_modis_ndvi, read_sentinel2, read_slga x3, read_srtm, read_worldclim): # before backend <- rlang::arg_match(tolower(backend)) # after backend <- tolower(backend) backend <- rlang::arg_match(backend) This preserves Sparks's input-normalisation intent while passing a bare symbol to arg_match. Behaviour equivalent; tests now pass. Bug 2 — read_slga() typo: to_lower vs tolower ------ read_slga.R:70 called to_lower(stat) where to_lower() does not exist. Would raise at runtime. R CMD check flagged: 'no visible global function definition for to_lower'. Fixed to tolower alongside the restructure above. Lifecycle wiring NOTE fix ------ R CMD check noted 'Namespace in Imports field not imported from: lifecycle'. lifecycle::badge() is invoked from roxygen at Rd render time (not from R source), so R CMD check couldn't see the usage. Added '@importFrom lifecycle deprecated' to R/geefetch-package.R (the canonical usethis::use_lifecycle() placement) — this wires the Import visibly and gives us lifecycle::deprecated() as a default-value helper for future deprecation cycles. Regenerated NAMESPACE via devtools::document(). Verification ------ 'R CMD check --as-cran geefetch_0.0.0.9001.tar.gz': from: 1 ERROR + 4 NOTEs to: 0 ERROR, 0 WARNING, 2 NOTEs Remaining 2 NOTEs are both environment / first-submission, not code: - CRAN incoming feasibility: 'New submission; Version contains large components (0.0.0.9001)'. Expected for a dev-version pre-CRAN; disappears at 0.1.0 release. - HTML Tidy: local macOS system tidy is too old for CRAN's HTML validator. Passes in CRAN's own env. Tests: [ FAIL 0 | WARN 1 | SKIP 4 | PASS 496 ]. The 4 skips are the httptest2 cassette tests waiting for recapture.
urlchecker::url_check() flagged three URL issues in the install docs. All non-functional today but CRAN policy requires canonical URLs. - README R-Universe link: was /geefetch (404 until M2 registration lands); now / (org root, always resolves). - README Codecov badge link: codecov.io/gh/... -> app.codecov.io/gh/... (codecov.io now 301s there; CRAN rejects permanent redirects in docs). - README + vignettes/geefetch.Rmd install block: cloud.r-project.org -> cran.r-project.org (CRAN's canonical host; the 'cloud.' alias is a CDN edge and fails CRAN's own URL-canonical check on submission). urlchecker::url_check(): All URLs are correct.
…vements
Under Option A (repo-root .github/ is the live CI config for subdir-package
layouts), the duplicate tree under geefetch/.github/ was dead code —
excluded from the package tarball by .Rbuildignore ^\.github$ AND
ignored by GitHub Actions which only reads from repo root. Two divergent
trees invited the footgun of 'edit the wrong one'.
Cleanup:
- Port pkgdown.yaml improvements from the subdir copy (which was better
than repo-root) into the live .github/workflows/pkgdown.yaml:
* top-level 'permissions: read-all' (least privilege) with
job-level 'contents: write' only where needed
* concurrency block to cancel in-progress runs on re-push
* pin JamesIves/github-pages-deploy-action to @v4.5.0 instead of
floating @v4 (dependabot will bump cleanly)
- Delete geefetch/.github/workflows/{R-CMD-check,pkgdown,test-coverage}.yaml
(the first two were strict subsets or stale copies of repo-root; the
third was identical). Delete the empty parent geefetch/.github/.
No functional change to CI behaviour — repo-root workflows were always
the live ones. This is hygiene.
R CMD check on tarball still passes (the deleted directory was already
excluded via .Rbuildignore; removing it only affects the git tree, not
the build).
Two distinct bugs surfaced during Phase-6 live-API validation against a real GEE project. Both pre-existed on review/ahs (and arguably on main); only showed up when an authenticated end-to-end call was actually made. R CMD check on mocked tests never exercised them. Bug 1 — 403 on cross-project OAuth clients ------ Symptom: first read_modis_ndvi() call after gee_auth() returned GEE REST API error (HTTP 403). Google Earth Engine API has not been used in project <N1> before or it is disabled. where <N1> was the OAuth client's default quota project, *not* the project in the URL (<N2>, the user's resource project passed via gee_auth(project = ...)). Root cause: .rest_request() set Authorization + Content-Type only. When the OAuth token's implicit quota project differs from the resource project in the URL, Google's EE API charges the quota project — and if EE API isn't enabled there (common for OAuth clients issued under a different project), returns 403 with the quota project's ID in the error message. This is confusing because the URL path and the error message disagree on which project is being referenced. Fix: send X-Goog-User-Project: <project> as a request header. Documented at https://cloud.google.com/docs/authentication/rest#set-quota-project as the canonical way to override a token's default quota project from the client side. Now both the URL path project and the quota project agree on <N2>. Bug 2 — SpatRaster points at a deleted tempfile after .rest_compute_pixels ------ Symptom: read_modis_ndvi() returned without visible error, but the returned SpatRaster failed on any subsequent operation that re-reads the file (disk cache writeRaster, terra::plot of lazy values): [writeRaster] file does not exist: /private/var/folders/.../RtmpXXXXXX/fileYYYYYY.tif Root cause: .rest_compute_pixels() created a tempfile, wrote GeoTIFF bytes, called terra::rast(tmp), then registered on.exit(unlink(tmp)) so the tempfile was deleted as the function returned. terra::rast() creates a file-backed lazy pointer — the returned SpatRaster referenced a file that had already been removed by the time the caller touched it. Fix: after terra::rast(tmp), force an in-memory materialisation via `r <- r * 1`. terra treats arithmetic as a full read + new in-memory raster. The returned object no longer depends on the tempfile; on.exit cleanup becomes safe. Cost: a full read per call, which is the expected semantics for a read_*() function anyway (these return values, not references to ephemeral files). Both fixes land the live-API path at a working state for users whose OAuth client defaults to a different project than the one they want to bill — which is the common case when the OAuth consent was granted under one account (e.g. via gcloud) and the resource project was created under another, or when gargle's default client isn't registered under the user's working project. Without either fix, a correctly-authenticated user with a correctly-enabled EE API still can't extract data. Tests: mocked httptest2 cassettes (pending capture) will still work — the additional header is benign at replay time. Cassette recapture after this fix will include the header automatically.
…alls Front-loads the Authentication setup so future users don't repeat the debugging path that surfaced the X-Goog-User-Project + tempfile bugs in ec3ec10. The new docs explain Google Cloud's project number vs ID distinction, the OAuth-quota-vs-resource-project model, and a concrete HTTP 403 decision tree. vignettes/geefetch.Rmd: Authentication section rewritten end-to-end. Old: 4 lines on gee_auth() + a placeholder gee_status() output. New: a 7-step walkthrough split into pre-R Cloud setup (project, EE registration, API enable) and in-R authentication. Calls out the single-Google-account discipline as the #1 cause of 403 errors. Always passes the project NUMBER (not ID). Includes a 2-row troubleshooting table indexed on which project number appears in the error message: matching number means EE not enabled / not propagated; mismatched number means OAuth token's quota project disagrees with the resource project (cache-wipe + restart fix). Adds .Rprofile snippet for persistence and a service-account variant for non-interactive use. R/auth.R: ?gee_auth roxygen rewritten. - @description references vignette("geefetch") for the prerequisite Cloud setup. - @param email gains "should match the account that owns the project" note. - @param project gains a paragraph on number-vs-ID with an example and a link to the dashboard URL where the number appears. - New @section "Project number vs ID" explains the underlying Google Cloud / OAuth distinction in plain language. - New @section "Persisting the project" shows the .Rprofile pattern. - @examples updated to lead with the recommended explicit-number form. R/auth.R: gee_setup() rewritten. Old: 5 generic steps that gave the right URLs but no flow. New: 7 ordered steps that mirror the vignette: pick one account, create project, register with EE, enable EE API, authenticate in R (with the project NUMBER as a string), verify, persist via .Rprofile. Adds a Troubleshooting block at the bottom showing the cache-wipe + restart sequence for the cross-project 403 case. All console output is ASCII (em-dashes replaced with --) so the file passes R CMD check's portability checks. NEWS.md: 0.0.0.9001 entry expanded. - User-visible changes adds a bullet describing the rewritten auth docs ("closes the 'why did my setup take four hours?' gap"). - Bug fixes adds the X-Goog-User-Project header fix and the tempfile materialisation fix from ec3ec10, with full before/after symptom + root-cause description. - Bug fixes adds the rlang::arg_match restructure from e3c9ffe (omitted from the previous NEWS draft). - Install command in the user-visible changes section updated cran.r-project.org (was cloud.) to match the canonicalisation landed in 8175c7b. Verification: R CMD check --as-cran on the rebuilt tarball returns 0 errors, 0 warnings, 3 environment-only NOTEs (CRAN new-submission boilerplate, NTP-unavailable transient, local macOS HTML Tidy too old). Tests: PASS 497 / FAIL 0 / SKIP 4 (httptest2 cassettes still pending capture).
The roxygen 'r lifecycle::badge(...)' inline expansion produces an
\figure{lifecycle-experimental.svg}{...} macro in each Rd file.
R's help renderer looks for that SVG in the LOCAL package's
man/figures/ directory, not in lifecycle's installed assets. Without
the SVG present, every help page that carries a lifecycle badge
renders a broken image placeholder ("?" icon) where the badge
should be.
Ran usethis::use_lifecycle() which copies the four canonical badge
SVGs (stable, experimental, superseded, deprecated) into
man/figures/. All 17 exported-function help pages now render their
badge correctly.
This fix should have landed alongside 5154443 (governance surface,
which added the badge calls); missed because R CMD check doesn't
flag missing \figure{} targets and the Rd renders silently with a
broken-image placeholder.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the full v0.0.0.9001 audit line onto
mainas a clean fast-forward, together with the review-hygiene pass layered on top of it.What this lands
match.arg()torlang::arg_match()across the dispatcher and reader functionsclimessage styling and Air formatting throughouthttptest2REST mocks and a credential-scrubbingtests/testthat/setup.R, so tests never touch the live server or leak auth stateoptions(geefetch.cache = )opt-out fortargetsandmemoiseworkflowsCITATION.cff,codemeta.json, ADRs,API_STABILITY.md, security, support and contributing docs, lifecycle badgesReview items
Addresses #1 items 1 (HTTP mocking) and 3 (cache options). Item 5 (precompiled vignettes) and issue #3 (repository structure) follow in their own dedicated PRs.
Verification
R CMD check --as-cran: 0 errors, 0 warnings, 1 note (new submission). All tests pass. The note also lists one soil-data DOI inread_slgathat needs correcting, which is handled in the follow-up grounding PR.