diff --git a/.github/scripts/partisan-pin b/.github/scripts/partisan-pin index 6585d01..628b29c 100644 --- a/.github/scripts/partisan-pin +++ b/.github/scripts/partisan-pin @@ -5,4 +5,10 @@ # # Recon context lives in the auto-memory entry # `project_partisan_pin.md` and in issue #57 / #49. -58b56da84ed815063e39acf6616bcc01bc6809a5 +# +# Bumped 2026-07-23 (devel HEAD) to pick up partisan PR #349 +# ("Add a physical_path attribute to Replica", merged 2026-07-15), +# whose test_replicas asserts physical_path is populated whenever +# client_version() >= 6.1.0 — the assertion this repo's #101 / +# #100 work needs exercised in CI. +1356610d2a195e881ca55351db68f61e53c4edc4 diff --git a/Cargo.toml b/Cargo.toml index c77b04b..d80d5e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "baton-rs" version = "1.0.0-alpha.1" edition = "2021" license = "GPL-2.0" -description = "Rust reimplementation of baton, the iRODS metadata client. Wire-compatible with upstream baton 6.0.0." +description = "Rust reimplementation of baton, the iRODS metadata client. Wire-compatible with upstream baton 6.1.0." repository = "https://github.com/jmtcsngr/baton-rs" readme = "README.md" diff --git a/README.md b/README.md index 4d69417..491ae18 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Rust reimplementation of [baton](https://github.com/wtsi-npg/baton), the iRODS client focused on metadata operations via a single JSON interface. Targets -wire-compat with upstream baton **6.0.0**. +wire-compat with upstream baton **6.1.0**. **Status:** Under active development. The seven binaries are implemented and exercised against iRODS 4.2.7 / 4.3.4 / 4.3.5 in CI, plus integration runs @@ -57,7 +57,7 @@ without coordinating schema changes with baton-rs. See the ## Compatibility with upstream baton -baton-rs is wire-compatible with upstream baton 6.0.0 for the success +baton-rs is wire-compatible with upstream baton 6.1.0 for the success path and the in-band error path. Known divergences: - **Parse-error wire shape** — baton-rs emits a stand-alone synthetic @@ -111,7 +111,7 @@ environment variable: | Env var state | `--version` reports | |----------------------------------------|------------------------------------------------------------| | Unset (or empty string) | The baton-rs crate version (`Cargo.toml`'s `version`). | -| Set to any non-empty value | `BATON_COMPAT_VERSION` (e.g. `6.0.0`) — the upstream baton release baton-rs targets wire-compat with. | +| Set to any non-empty value | `BATON_COMPAT_VERSION` (e.g. `6.1.0`) — the upstream baton release baton-rs targets wire-compat with. | Honest reporting is the default so logs and debugging surfaces aren't misled about what's actually running. The compat mode exists for downstream consumers @@ -125,7 +125,7 @@ $ baton-do --version 1.0.0-alpha.1 $ STRICT_BATON_COMPAT=1 baton-do --version -6.0.0 +6.1.0 ``` The `STRICT_BATON_COMPAT` toggle is also reserved for future wire-format diff --git a/SESSIONS.md b/SESSIONS.md index 107b8f1..2e32f8d 100644 --- a/SESSIONS.md +++ b/SESSIONS.md @@ -10,7 +10,7 @@ Paste the relevant sections at the start of each new session to restore context These values do not change during normal development. If they do change, note the reason here. -- **Target baton version for parity:** `6.0.0` +- **Target baton version for parity:** `6.1.0` _(bumped from `6.0.0` 2026-07-23 — upstream released 6.0.1 and 6.1.0; see Changelog for this file, below, for what the re-pin required.)_ - **Reference schema:** - **Primary compatibility oracle:** partisan's Python test suite, run with baton-rs's `baton-do` on `PATH` - **Supported iRODS versions (CI matrix):** 4.2.7, 4.3.4, 4.3.5 _(add 5.0.1 when ready)_ @@ -583,3 +583,8 @@ Use this space to record non-trivial changes to the plan itself — e.g. changin - **#82** — extendo pin bumped to `v3.1.0` (`b24b63c…`, against `feat/extendo-ci` base). Tagged release over devel HEAD. - **#83** — synthetic-`-1` audit cleanup across 17 validation paths. Group A (target-type) and B (local-FS) surface `-310000` (USER_FILE_DOES_NOT_EXIST); Group C (argument validation) surfaces `-403000` (USER_INPUT_OPTION_ERR). Adds `-403000` to `error::descriptive_phrase`. - **#85** — client-side `verify_checksum` mismatch aligned to `-407000` (CHECK_VERIFICATION_RESULTS). Final audit follow-up; client-side and server-side mismatch now surface under the same code. +- `2026-07-23` — Target baton version for parity bumped `6.0.0` → `6.1.0` on branch `chore/bump-baton-compat-6.1.0` (PR #102), landed as a compat *feature*, not a version-string-only chore. Upstream had shipped two releases we hadn't tracked: `6.0.1` (2026-05-19, mostly CI/dependency churn plus two OOB-write hardening fixes in its own genQuery builder, wtsi-npg/baton#337/#338 — see the #100 note below for why that turned out not to be baton-rs's problem) and `6.1.0` (2026-07-02, adds a `physical_path` field to replicate JSON objects, wtsi-npg/baton#339). Two gaps were filed against the re-pin; only one was real: + - **#100 — filed, then retracted as a false premise.** Initial analysis assumed `genQueryInp_t.sqlCondInp` / `.selectInp` were fixed-size iRODS arrays that `addInxVal` / `addInxIval` could write past, mirroring upstream baton's 6.0.1 OOB-write fix (wtsi-npg/baton#337/#338). A bounds check (`len >= MAX_NUM_CONDITIONS`) was added to the shim — which failed CI immediately: `MAX_NUM_CONDITIONS` isn't an iRODS macro at all. Checking the actual iRODS source (`irods/irods`) showed `inxIvalPair_t` / `inxValPair_t` (`lib/core/include/irods/objInfo.h`) are pointer-based, and `addInxIval` / `addInxVal` (`lib/core/src/rcMisc.cpp`) dynamically grow their backing storage via `malloc` — there is no fixed capacity to overflow. `MAX_NUM_CONDITIONS = 32` is baton's *own* self-imposed limit (`baton/src/query.h`), guarding a fixed-size buffer in baton's own C code, not the shared iRODS client library. Since baton-rs's shim calls `addInxIval` / `addInxVal` directly with no intermediate fixed-size staging array, it never had the vulnerability baton patched. All #100 code (the bounds check, `GenQuery::add_select`'s `Result` return, the 25 call-site `?` additions, and the new test) was reverted; the issue was corrected on GitHub rather than silently dropped, and the finding recorded in memory (`feedback_compat_version_claims.md`'s sibling note) as a reminder to verify upstream C internals before porting a "security fix" description across languages/architectures. + - **#101** — added `SHIM_COL_DATA_PATH` (→ `COL_D_DATA_PATH`) through the standard shim declare/implement/mirror path, a new `Replicate.physical_path: String` field, and wired it into `fetch_replicates`. Extended the existing `--replicate` integration test and the `Replicate` JSON round-trip unit test. + - **Caught mid-flight:** an initial pass bumped `BATON_COMPAT_VERSION` (and the Cargo.toml/README "wire-compatible with X" claims) to 6.1.0 *before* implementing either gap — the user caught this as a compat lie (`STRICT_BATON_COMPAT=1 baton-do --version` is read by real callers, not just documentation) before it merged. Recorded as a standing rule in memory (`feedback_compat_version_claims.md`): never bump the operational compat-version claim ahead of implementing that version's schema/behaviour changes. + - **Partisan pin bumped** (`.github/scripts/partisan-pin`, `58b56da` → `1356610d2a`, devel HEAD) to pick up partisan PR #349 ("Add a physical_path attribute to Replica", merged 2026-07-15), whose `test_replicas` asserts `physical_path is not None` whenever `client_version() >= 6.1.0` — the exact assertion this work needed exercised in CI. Extendo's pin was checked and left alone: no physical_path work exists there yet (`v3.1.0..v3.2.0` diff is dependency bumps only). diff --git a/shim/ffi_shim.c b/shim/ffi_shim.c index 55c1ced..bbd9486 100644 --- a/shim/ffi_shim.c +++ b/shim/ffi_shim.c @@ -226,6 +226,7 @@ static int translate_col(shim_col_t col) { case SHIM_COL_COLL_USER_ZONE: return COL_COLL_USER_ZONE; case SHIM_COL_COLL_ACCESS_NAME: return COL_COLL_ACCESS_NAME; case SHIM_COL_DATA_SIZE: return COL_DATA_SIZE; + case SHIM_COL_DATA_PATH: return COL_D_DATA_PATH; } return -1; } diff --git a/shim/ffi_shim.h b/shim/ffi_shim.h index 43a4d31..79aca95 100644 --- a/shim/ffi_shim.h +++ b/shim/ffi_shim.h @@ -173,6 +173,7 @@ typedef enum { SHIM_COL_COLL_USER_ZONE = 23, SHIM_COL_COLL_ACCESS_NAME = 24, SHIM_COL_DATA_SIZE = 25, + SHIM_COL_DATA_PATH = 26, } shim_col_t; // Allocate a fresh query builder. Returns NULL on out-of-memory. diff --git a/src/ffi.rs b/src/ffi.rs index bc0a5ac..036613b 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -107,6 +107,7 @@ pub const SHIM_COL_COLL_USER_NAME: shim_col_t = 22; pub const SHIM_COL_COLL_USER_ZONE: shim_col_t = 23; pub const SHIM_COL_COLL_ACCESS_NAME: shim_col_t = 24; pub const SHIM_COL_DATA_SIZE: shim_col_t = 25; +pub const SHIM_COL_DATA_PATH: shim_col_t = 26; // ---- Data object read/write ------------------------------------------------- diff --git a/src/operations/list.rs b/src/operations/list.rs index 1f33ea2..ff7bbb6 100644 --- a/src/operations/list.rs +++ b/src/operations/list.rs @@ -466,6 +466,7 @@ fn fetch_replicates( q.add_select(ffi::SHIM_COL_R_LOC); q.add_select(ffi::SHIM_COL_D_RESC_NAME); q.add_select(ffi::SHIM_COL_D_REPL_STATUS); + q.add_select(ffi::SHIM_COL_DATA_PATH); q.add_where( ffi::SHIM_COL_COLL_NAME, @@ -485,6 +486,7 @@ fn fetch_replicates( let location = row.get(2).cloned().unwrap_or_default(); let resource = row.get(3).cloned().unwrap_or_default(); let status = row.get(4).cloned().unwrap_or_default(); + let physical_path = row.get(5).cloned().unwrap_or_default(); let number: u32 = number_str.parse().map_err(|_| BatonError { code: -1, @@ -497,6 +499,7 @@ fn fetch_replicates( resource, number, valid: status == "1", + physical_path, }) }) .collect() diff --git a/src/types.rs b/src/types.rs index 297445a..ed268f2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,11 @@ // Generated by: Claude Opus 4.7 (1M context) // Generation date: 2026-04-24 -// Last modified by: Claude Opus 4.7 (1M context) -// Last modified: 2026-05-12 +// Last modified by: Claude Sonnet 5 (1M context) +// Last modified: 2026-07-23 //! JSON data types for the baton-rs API. //! -//! All types round-trip against the baton 6.0.0 JSON schema +//! All types round-trip against the baton 6.1.0 JSON schema //! (). use serde::de::{self, MapAccess, Visitor}; @@ -106,6 +106,13 @@ pub struct Replicate { pub resource: String, pub number: u32, pub valid: bool, + + /// The replica's physical path on the resource server's + /// filesystem, sourced from `COL_D_DATA_PATH`. Added in baton + /// 6.1.0 (wtsi-npg/baton#339); baton-rs always populates it, so + /// unlike upstream pre-6.1.0 servers there's no "missing key" + /// case to model here. + pub physical_path: String, } /// A timestamp for a data object or replicate. @@ -1391,13 +1398,14 @@ mod tests { #[test] fn replicate_round_trip() { - let json = r#"{"checksum":"d41d8cd98f00b204e9800998ecf8427e","location":"host","resource":"demoResc","number":0,"valid":true}"#; + let json = r#"{"checksum":"d41d8cd98f00b204e9800998ecf8427e","location":"host","resource":"demoResc","number":0,"valid":true,"physical_path":"/var/lib/irods/Vault/home/irods/foo.txt"}"#; let r: Replicate = serde_json::from_str(json).unwrap(); assert_eq!(r.checksum, "d41d8cd98f00b204e9800998ecf8427e"); assert_eq!(r.location, "host"); assert_eq!(r.resource, "demoResc"); assert_eq!(r.number, 0); assert!(r.valid); + assert_eq!(r.physical_path, "/var/lib/irods/Vault/home/irods/foo.txt"); assert_eq!(serde_json::to_string(&r).unwrap(), json); } diff --git a/src/version.rs b/src/version.rs index 66c3c11..7d9f6a1 100644 --- a/src/version.rs +++ b/src/version.rs @@ -35,7 +35,7 @@ /// recorded in `SESSIONS.md` whenever we re-pin against a newer /// upstream baton. The release checklist in #58 calls out this /// audit step explicitly. -pub const BATON_COMPAT_VERSION: &str = "6.0.0"; +pub const BATON_COMPAT_VERSION: &str = "6.1.0"; /// Env var that toggles compat-mode version reporting. See module /// doc-comment for the rationale and #58 for the design. diff --git a/tests/baton_do_binary.rs b/tests/baton_do_binary.rs index a83e7cc..9b65a3e 100644 --- a/tests/baton_do_binary.rs +++ b/tests/baton_do_binary.rs @@ -1225,7 +1225,7 @@ fn version_flag_unset_reports_crate_version() { #[test] fn version_flag_with_strict_compat_reports_compat_version() { // STRICT_BATON_COMPAT=1 → report BATON_COMPAT_VERSION instead - // (currently 6.0.0). Lets partisan's `client_version()` parse + // (currently 6.1.0). Lets partisan's `client_version()` parse // the result and compare it against expected baton X.Y.Z values. let mut child = Command::new(baton_do_path()) .args(["--version"]) diff --git a/tests/list.rs b/tests/list.rs index 0cd87b2..2d584ae 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -628,6 +628,13 @@ fn list_data_object_with_replicate() { .expect("replica 0 present"); assert!(!r0.resource.is_empty(), "resource set"); assert!(!r0.location.is_empty(), "location set"); + // physical_path (baton 6.1.0, #101 / COL_D_DATA_PATH) is a real + // filesystem path under the test container's iRODS vault. + assert!( + r0.physical_path.starts_with("/var/lib/irods"), + "expected physical_path under the iRODS vault, got {:?}", + r0.physical_path + ); // Every replica must be valid right after iput. This is stricter // than the original "r0 is valid" form: on the 4.2.7 CI image