From ad0ad10d9784a3ff9e35fd722ea98e2c5b06c8f9 Mon Sep 17 00:00:00 2001 From: Yuanhao Li Date: Sat, 11 Jul 2026 13:56:18 +0200 Subject: [PATCH 1/3] ci: add CI with an MSRV-enforcement job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had no PR CI at all — only the publish workflow. Two jobs: - Check & Test (stable): fmt --check, clippy --all-targets under -Dwarnings, cargo test (with a git identity for the git_store tests). - MSRV (1.85): cargo check --all-targets on the DECLARED rust-version. Cargo never verifies rust-version against the code — 0.4.0/0.4.1 shipped a let-chain (needs Rust 1.88) while declaring 1.85, silently breaking downstream MSRV builds (yologdev/yoagent#68). This job makes that class of drift impossible: any future construct above 1.85 fails the PR. Also brings the tree up to the new gates: cargo fmt over src/state.rs, and the test-side DoubleEndedIterator lint fixed (.filter().last() -> .rfind()). Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ src/state.rs | 11 +++++------ tests/failure_paths.rs | 11 +++++++++-- tests/run_chaining.rs | 6 +----- 4 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0109b5d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-Dwarnings" + +jobs: + check-test: + name: Check & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Format + run: cargo fmt -- --check + - name: Clippy + run: cargo clippy --all-targets + - name: Test + run: | + git config --global user.email "ci@yolog.dev" + git config --global user.name "yoagent-state-ci" + cargo test + + # Enforce the DECLARED rust-version with a real compiler. Cargo never + # verifies rust-version against the code — 0.4.0/0.4.1 shipped a let-chain + # (needs 1.88) while declaring 1.85, breaking downstream MSRV builds + # (yologdev/yoagent#68). This job makes that class of lie impossible. + msrv: + name: MSRV (1.85) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.85 + - uses: Swatinem/rust-cache@v2 + - name: Check + run: cargo check --all-targets diff --git a/src/state.rs b/src/state.rs index 1ecee72..fe18732 100644 --- a/src/state.rs +++ b/src/state.rs @@ -56,12 +56,11 @@ impl YoAgentState { let events = self.store.scan().await?; let mut open: Option<(RunId, EventId)> = None; for event in &events { - let run_id = event - .payload - .get("run_id") - .and_then(|v| v.as_str().map(RunId::from).or_else(|| { - serde_json::from_value::(v.clone()).ok() - })); + let run_id = event.payload.get("run_id").and_then(|v| { + v.as_str() + .map(RunId::from) + .or_else(|| serde_json::from_value::(v.clone()).ok()) + }); match (event.kind.as_str(), run_id) { ("run.started", Some(id)) => open = Some((id, event.id.clone())), ("run.finished", Some(id)) => { diff --git a/tests/failure_paths.rs b/tests/failure_paths.rs index 1d93e2d..7587909 100644 --- a/tests/failure_paths.rs +++ b/tests/failure_paths.rs @@ -147,12 +147,19 @@ async fn resume_open_run_recovers_the_marker_across_processes() { // events in the resumed process chain + correlate to the original run state2 - .record_event(Event::new(actor(), "observation.created", json!({"id": "o1"}))) + .record_event(Event::new( + actor(), + "observation.created", + json!({"id": "o1"}), + )) .await .unwrap(); let events = state2.store().scan().await.unwrap(); let started = events.iter().find(|e| e.kind == "run.started").unwrap(); - let obs = events.iter().find(|e| e.kind == "observation.created").unwrap(); + let obs = events + .iter() + .find(|e| e.kind == "observation.created") + .unwrap(); assert_eq!(obs.causation_id.as_ref(), Some(&started.id)); assert_eq!(obs.correlation_id.as_deref(), Some("run_x")); diff --git a/tests/run_chaining.rs b/tests/run_chaining.rs index 59ba495..7d91faf 100644 --- a/tests/run_chaining.rs +++ b/tests/run_chaining.rs @@ -75,11 +75,7 @@ async fn events_inside_a_run_chain_to_run_started() { .await .unwrap(); let events = state.store().scan().await.unwrap(); - let after = events - .iter() - .filter(|e| e.kind == "goal.created") - .last() - .unwrap(); + let after = events.iter().rfind(|e| e.kind == "goal.created").unwrap(); assert_eq!(after.causation_id, None); assert_eq!(after.correlation_id, None); } From dd426a4e5a5d47ecabdc3cc551a13c079a788f97 Mon Sep 17 00:00:00 2001 From: Yuanhao Li Date: Sat, 11 Jul 2026 13:58:31 +0200 Subject: [PATCH 2/3] fix: collapse resume_open_run's nested if into a match guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI runner's newer clippy (1.97) flags collapsible_match here. Safe: guard fall-through lands on the catch-all no-op arm — identical semantics to the nested if (verified by the run_chaining tests). Co-Authored-By: Claude Fable 5 --- src/state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/state.rs b/src/state.rs index fe18732..b7f0e03 100644 --- a/src/state.rs +++ b/src/state.rs @@ -63,10 +63,10 @@ impl YoAgentState { }); match (event.kind.as_str(), run_id) { ("run.started", Some(id)) => open = Some((id, event.id.clone())), - ("run.finished", Some(id)) => { - if open.as_ref().is_some_and(|(o, _)| *o == id) { - open = None; - } + // Guard fall-through hits the `_ => {}` arm — same no-op as + // the previous nested if. + ("run.finished", Some(id)) if open.as_ref().is_some_and(|(o, _)| *o == id) => { + open = None; } _ => {} } From d2f0024d1f012a8ae496f3d7883019fce710cf6a Mon Sep 17 00:00:00 2001 From: Yuanhao Li Date: Sat, 11 Jul 2026 14:10:57 +0200 Subject: [PATCH 3/3] ci: enforce the committed lock (--locked), concurrency, permissions, timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review findings: uuid/getrandom sit at exactly rust-version 1.85 — without --locked, a lock regeneration could pull a 1.86-MSRV patch and silently break (or mask regressions in) the 1.85 gate. Also: cancel superseded runs, least-privilege token, and job timeouts. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0109b5d..7454d38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,18 @@ env: CARGO_TERM_COLOR: always RUSTFLAGS: "-Dwarnings" +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: check-test: name: Check & Test runs-on: ubuntu-latest + timeout-minutes: 20 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -20,12 +28,12 @@ jobs: - name: Format run: cargo fmt -- --check - name: Clippy - run: cargo clippy --all-targets + run: cargo clippy --locked --all-targets - name: Test run: | git config --global user.email "ci@yolog.dev" git config --global user.name "yoagent-state-ci" - cargo test + cargo test --locked # Enforce the DECLARED rust-version with a real compiler. Cargo never # verifies rust-version against the code — 0.4.0/0.4.1 shipped a let-chain @@ -34,9 +42,13 @@ jobs: msrv: name: MSRV (1.85) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.85 - uses: Swatinem/rust-cache@v2 - name: Check - run: cargo check --all-targets + # --locked: uuid/getrandom sit at exactly rust-version 1.85 (zero + # headroom) — enforce the committed lock so a regeneration can't + # silently move the gate. + run: cargo check --locked --all-targets