Skip to content

feat(trogon-std): add CreateDirAll and OpenAppendFile fs traits#8

Merged
yordis merged 1 commit into
mainfrom
add-create-dir-all
Feb 23, 2026
Merged

feat(trogon-std): add CreateDirAll and OpenAppendFile fs traits#8
yordis merged 1 commit into
mainfrom
add-create-dir-all

Conversation

@yordis

@yordis yordis commented Feb 23, 2026

Copy link
Copy Markdown
Member

Summary

  • Add CreateDirAll trait for recursive directory creation
  • Add OpenAppendFile trait for opening files in append mode with an associated Writer type
  • Implement both traits on SystemFs (delegates to std::fs) and MemFs (in-memory test double)

Test plan

  • Unit tests for MemFs::create_dir_all (creates ancestor dirs, idempotent)
  • Verify cargo test passes in trogon-std

@cursor

cursor Bot commented Feb 23, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Introduces new public API traits and changes MemFs’ internal mutability/concurrency model, which could affect test behavior and downstream compilation for consumers adopting the new traits.

Overview
Adds two new filesystem capabilities to trogon-std: CreateDirAll for recursive directory creation and OpenAppendFile for opening a writable append handle.

Implements both traits for SystemFs (delegating to std::fs) and extends MemFs to support append writers and directory tracking; MemFs’ internal storage is moved to Arc<Mutex<...>> to allow the returned append writer to persist writes. Updates module exports and crate re-exports, and adds unit tests covering append persistence and create_dir_all success/idempotency/error cases.

Written by Cursor Bugbot for commit 5d49d27. This will update automatically on new commits. Configure here.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@yordis has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 19 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 516536a and 5d49d27.

📒 Files selected for processing (6)
  • rsworkspace/crates/trogon-std/src/fs/create_dir_all.rs
  • rsworkspace/crates/trogon-std/src/fs/mem.rs
  • rsworkspace/crates/trogon-std/src/fs/mod.rs
  • rsworkspace/crates/trogon-std/src/fs/open_append_file.rs
  • rsworkspace/crates/trogon-std/src/fs/system.rs
  • rsworkspace/crates/trogon-std/src/lib.rs

Walkthrough

This pull request introduces two new public trait abstractions to the trogon-std filesystem module: CreateDirAll for directory creation with parent propagation, and OpenAppendFile for append-mode file operations. Both traits are implemented for SystemFs (delegating to standard library functions) and MemFs (recording operations in internal collections for testing). The MemFs struct gains tracking for directories and opened files via internal HashSet collections.

Changes

Cohort / File(s) Summary
New Trait Definitions
rsworkspace/crates/trogon-std/src/fs/create_dir_all.rs, rsworkspace/crates/trogon-std/src/fs/open_append_file.rs
Introduces CreateDirAll trait with create_dir_all() method and OpenAppendFile trait with associated Writer type and open_append() method for filesystem abstraction.
Module Exports
rsworkspace/crates/trogon-std/src/fs/mod.rs
Re-exports new CreateDirAll and OpenAppendFile traits as public APIs alongside existing filesystem trait exports.
SystemFs Implementation
rsworkspace/crates/trogon-std/src/fs/system.rs
Implements CreateDirAll (delegates to std::fs::create_dir_all) and OpenAppendFile (uses File::options for append operations) for system filesystem operations.
MemFs Enhancement
rsworkspace/crates/trogon-std/src/fs/mem.rs
Adds dirs and opened_files HashSet tracking fields; implements CreateDirAll and OpenAppendFile traits; adds dir_exists() and was_opened() query methods; includes test cases for directory creation validation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Two traits hop into the forest,
SystemFs and MemFs in chorus,
Appending files and dirs we create,
Filesystem abstraction—how ornate! 🌲✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding two new filesystem traits (CreateDirAll and OpenAppendFile) to the trogon-std crate.
Description check ✅ Passed The description provides a clear summary of the changes including the two traits being added and their implementations on SystemFs and MemFs, with a test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-create-dir-all

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread rsworkspace/crates/trogon-std/src/fs/mem.rs
Comment thread rsworkspace/crates/trogon-std/src/fs/mem.rs
Comment thread rsworkspace/crates/trogon-std/src/fs/open_append_file.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
rsworkspace/crates/trogon-std/src/fs/mem.rs (2)

260-278: Add a test covering was_opened.

was_opened and the side-effect of open_append recording the path are entirely untested. The two existing tests only cover create_dir_all.

🧪 Suggested test
+    #[test]
+    fn test_memfs_open_append_records_path() {
+        let fs = MemFs::new();
+        let path = Path::new("/logs/app.log");
+
+        assert!(!fs.was_opened(path));
+        fs.open_append(path).unwrap();
+        assert!(fs.was_opened(path));
+        assert!(!fs.was_opened(Path::new("/logs/other.log")));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/trogon-std/src/fs/mem.rs` around lines 260 - 278, Add a
unit test in the same mem.rs test module that constructs a MemFs, calls
open_append on a file path (e.g., Path::new("/foo/bar.txt")), then asserts that
MemFs::was_opened(&path) returns true and that subsequent calls (e.g., opening
again or create_dir_all) behave idempotently; specifically exercise
MemFs::open_append and MemFs::was_opened to verify the side-effect of recording
opened paths is implemented. Use the existing test style (creating MemFs with
MemFs::new and Path::new) and include negative assertions for paths not opened
to ensure correctness.

108-116: MemFs::open_append returns a disconnected Vec<u8> — written content is unobservable.

Every call returns a fresh, owned Vec<u8>. MemFs holds no reference to it, so any bytes the code-under-test writes (e.g., writer.write_all(b"...")) are permanently lost. was_opened can only assert that the file was opened, not what was written. For a test double covering the full append contract, callers have no way to verify written content.

Consider replacing Writer = Vec<u8> with a shared-buffer newtype:

♻️ Suggested approach: shared observable buffer
+#[cfg(any(test, feature = "test-support"))]
+#[derive(Clone, Default)]
+pub struct SharedBuffer(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
+
+#[cfg(any(test, feature = "test-support"))]
+impl SharedBuffer {
+    pub fn into_bytes(self) -> Vec<u8> {
+        self.0.lock().unwrap().clone()
+    }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+impl io::Write for SharedBuffer {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0.lock().unwrap().write(buf)
+    }
+    fn flush(&mut self) -> io::Result<()> { Ok(()) }
+}

 #[cfg(any(test, feature = "test-support"))]
 impl OpenAppendFile for MemFs {
-    type Writer = Vec<u8>;
+    type Writer = SharedBuffer;

     fn open_append(&self, path: &Path) -> io::Result<Self::Writer> {
+        let buf = SharedBuffer::default();
         self.opened_files.borrow_mut().insert(path.to_path_buf());
-        Ok(Vec::new())
+        // optionally also store buf.clone() keyed by path for post-call inspection
+        Ok(buf)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/trogon-std/src/fs/mem.rs` around lines 108 - 116, The test
double currently implements OpenAppendFile for MemFs with Writer = Vec<u8> and
open_append returning a fresh Vec that MemFs never stores, so written bytes are
lost; change Writer to a shared buffer type (e.g., Rc<RefCell<Vec<u8>>> or
Arc<Mutex<Vec<u8>>>) and have open_append create or retrieve a shared buffer
stored inside MemFs (e.g., keyed by path in the opened_files or a new map
field), return that shared buffer as the Writer, and provide a way for tests to
inspect the buffer contents; update the OpenAppendFile impl, the open_append
method, and any tests to use the new shared-buffer Writer so writes are
observable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@rsworkspace/crates/trogon-std/src/fs/mem.rs`:
- Around line 260-278: Add a unit test in the same mem.rs test module that
constructs a MemFs, calls open_append on a file path (e.g.,
Path::new("/foo/bar.txt")), then asserts that MemFs::was_opened(&path) returns
true and that subsequent calls (e.g., opening again or create_dir_all) behave
idempotently; specifically exercise MemFs::open_append and MemFs::was_opened to
verify the side-effect of recording opened paths is implemented. Use the
existing test style (creating MemFs with MemFs::new and Path::new) and include
negative assertions for paths not opened to ensure correctness.
- Around line 108-116: The test double currently implements OpenAppendFile for
MemFs with Writer = Vec<u8> and open_append returning a fresh Vec that MemFs
never stores, so written bytes are lost; change Writer to a shared buffer type
(e.g., Rc<RefCell<Vec<u8>>> or Arc<Mutex<Vec<u8>>>) and have open_append create
or retrieve a shared buffer stored inside MemFs (e.g., keyed by path in the
opened_files or a new map field), return that shared buffer as the Writer, and
provide a way for tests to inspect the buffer contents; update the
OpenAppendFile impl, the open_append method, and any tests to use the new
shared-buffer Writer so writes are observable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2030ec6 and 516536a.

📒 Files selected for processing (5)
  • rsworkspace/crates/trogon-std/src/fs/create_dir_all.rs
  • rsworkspace/crates/trogon-std/src/fs/mem.rs
  • rsworkspace/crates/trogon-std/src/fs/mod.rs
  • rsworkspace/crates/trogon-std/src/fs/open_append_file.rs
  • rsworkspace/crates/trogon-std/src/fs/system.rs

@yordis yordis force-pushed the add-create-dir-all branch 2 times, most recently from 6b5e53e to 434aaf5 Compare February 23, 2026 21:11

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Comment thread rsworkspace/crates/trogon-std/src/fs/mod.rs
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the add-create-dir-all branch from 434aaf5 to 5d49d27 Compare February 23, 2026 21:19
@yordis yordis merged commit 54c0d00 into main Feb 23, 2026
4 checks passed
@yordis yordis deleted the add-create-dir-all branch February 23, 2026 21:29
jramirezhdez02 added a commit that referenced this pull request Feb 27, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
jramirezhdez02 added a commit that referenced this pull request Apr 7, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha pushed a commit that referenced this pull request Apr 7, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
jramirezhdez02 added a commit that referenced this pull request Apr 9, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha pushed a commit that referenced this pull request Apr 9, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha pushed a commit that referenced this pull request Apr 10, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
jramirezhdez02 added a commit that referenced this pull request Apr 13, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha pushed a commit that referenced this pull request Apr 13, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha added a commit that referenced this pull request Apr 14, 2026
…op_reason → Failed

#7 — split max_tokens out of the catch-all stop_reason arm (keeps
PermanentFailed) and change the true `other` arm to write `Failed`
instead of PermanentFailed. A future Anthropic stop_reason that is
transient would previously be discarded forever; now NATS redelivery
can retry it up to the stream's delivery limit.

#8 — add `recovery_count: u32` (#[serde(default)]) to AgentPromise.
Each time startup recovery re-claims a stale Running promise the field
is incremented. At MAX_RECOVERY_ATTEMPTS (5) the promise is marked
PermanentFailed and the run is skipped, breaking the infinite cycle
caused by deterministic crashes that prevent try_mark_permanent_failed_fresh
from writing. Failed promises (NATS redelivery path) are not counted.

Tests added: max_tokens_stop_reason_marks_promise_permanent_failed,
unknown_stop_reason_marks_promise_failed_not_permanent,
prepare_agent_over_recovery_limit_marks_permanent_failed_and_returns_none,
prepare_agent_running_reclaim_increments_recovery_count.
mariorha pushed a commit that referenced this pull request Apr 20, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
mariorha pushed a commit that referenced this pull request Apr 20, 2026
- backoff_shift_saturates_at_31_for_high_attempt_counts: verifies the
  formula (attempts-1).min(31) saturates the shift count at 31 for all
  attempt values >= 32, preventing u32 overflow in Duration arithmetic
- e2e_empty_query_string_appended_as_bare_question_mark: documents that
  a URI with trailing '?' (empty query) causes uri.query() = Some(""),
  which the proxy formats as "?" and appends to the upstream URL

Gaps #1 (double publish failure) and #8 (consumer stream error) are not
testable without introducing a trait abstraction over async_nats::Client.
yordis added a commit that referenced this pull request May 26, 2026
…auth callout

Task #8 needs proof that real server authorization requests reach the
dispatcher and mint caller ACLs before operator rollout; wire decoding
was aligned with how nats-server actually signs and omits JWT fields.

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
yordis added a commit that referenced this pull request May 26, 2026
…test

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant