diff --git a/crates/tui/src/commands/mod.rs b/crates/tui/src/commands/mod.rs index d6b34d8f..f60786b9 100644 --- a/crates/tui/src/commands/mod.rs +++ b/crates/tui/src/commands/mod.rs @@ -464,7 +464,7 @@ pub fn rlm(app: &mut App, arg: Option<&str>) -> CommandResult { child_model, max_depth, ), - AppAction::RlmQuery { + AppAction::Rlm { prompt, model, child_model, diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index b1fbb067..bdff9185 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -412,8 +412,8 @@ fn should_default_defer_tool(name: &str, mode: AppMode) -> bool { | "grep_files" | "file_search" | "diagnostics" - | "rlm_query" - | "rlm_process" + | "parallel_fanout" + | "rlm" | MULTI_TOOL_PARALLEL_NAME | "update_plan" | "todo_write" @@ -1312,13 +1312,13 @@ impl Engine { Op::CompactContext => { self.handle_manual_compaction().await; } - Op::RlmQuery { + Op::Rlm { content, model, child_model, max_depth, } => { - self.handle_rlm_query(content, model, child_model, max_depth) + self.handle_rlm(content, model, child_model, max_depth) .await; } Op::Shutdown => { @@ -1450,8 +1450,8 @@ impl Engine { builder = builder .with_review_tool(self.deepseek_client.clone(), self.session.model.clone()) - .with_rlm_query_tool(self.deepseek_client.clone()) - .with_rlm_process_tool(self.deepseek_client.clone(), self.session.model.clone()) + .with_parallel_fanout_tool(self.deepseek_client.clone()) + .with_rlm_tool(self.deepseek_client.clone(), self.session.model.clone()) .with_user_input_tool() .with_parallel_tool(); @@ -1663,7 +1663,7 @@ impl Engine { /// only sees metadata about the REPL state, never the prompt text /// directly. The model generates Python code, which is executed by /// the REPL. When FINAL() is called, the loop ends. - async fn handle_rlm_query( + async fn handle_rlm( &mut self, content: String, model: String, diff --git a/crates/tui/src/core/ops.rs b/crates/tui/src/core/ops.rs index 00bfc55f..051b449a 100644 --- a/crates/tui/src/core/ops.rs +++ b/crates/tui/src/core/ops.rs @@ -67,8 +67,8 @@ pub enum Op { /// Run a Recursive Language Model (RLM) turn per Algorithm 1 of /// Zhang et al. (arXiv:2512.24601). The prompt is stored in the REPL - /// as the `PROMPT` variable; the root LLM only sees metadata. - RlmQuery { + /// as `context`; the root LLM only sees metadata. + Rlm { /// The user's prompt — stored in REPL, NOT in the LLM context. content: String, /// The model to use for root LLM calls. diff --git a/crates/tui/src/prompts/agent.txt b/crates/tui/src/prompts/agent.txt index b7834179..b58dbfbf 100644 --- a/crates/tui/src/prompts/agent.txt +++ b/crates/tui/src/prompts/agent.txt @@ -1,6 +1,6 @@ ## Mode: agent -Read-only tools (reads, searches, `rlm_query`, agent status queries, git inspection) run silently. +Read-only tools (reads, searches, `parallel_fanout`, agent status queries, git inspection) run silently. Any write, patch, shell execution, sub-agent spawn, or CSV batch operation will ask for approval first. Before requesting approval for writes, lay out your work with `todo_write` so the user can see what diff --git a/crates/tui/src/prompts/base.txt b/crates/tui/src/prompts/base.txt index 939575dc..521a9293 100644 --- a/crates/tui/src/prompts/base.txt +++ b/crates/tui/src/prompts/base.txt @@ -9,7 +9,7 @@ Your default workflow for any non-trivial request: 2. **Execute** — work through each todo, updating status as you go. 3. **For complex initiatives**, layer `update_plan` (high-level strategy) above `todo_write` (granular steps). 4. **For parallel work**, spawn sub-agents (`agent_spawn` / `agent_swarm`) — each does one thing well. Link them to plan/todo items in your thinking. -5. **For LM-only fan-out** (summarization, classification, analysis across many items), use `rlm_query` for fast parallel inference. +5. **For LM-only fan-out** (summarization, classification, analysis across many items), use `parallel_fanout` for fast parallel inference. 6. **For persistent cross-session memory**, use `note` sparingly for important decisions, open blockers, and architectural context. **Key principle**: make your work visible. The sidebar shows Plan / Todos / Tasks / Agents. When these panels are empty, the user has no idea what you're doing. Keep them populated. @@ -28,7 +28,7 @@ Model notes: DeepSeek V4 models emit *thinking tokens* (`ContentBlock::Thinking` - **Git / diag / tests**: `git_status`, `git_diff`, `git_show`, `git_log`, `git_blame`, `diagnostics`, `run_tests`, `review`. - **Sub-agents**: `agent_spawn` (`spawn_agent`, `delegate_to_agent`), `agent_swarm`, `agent_result`, `agent_cancel` (`close_agent`), `agent_list`, `agent_wait` (`wait`), `agent_send_input` (`send_input`), `agent_assign` (`assign_agent`), `resume_agent`. - **CSV batch**: `spawn_agents_on_csv`, `report_agent_job_result`. -- **LM fan-out**: `rlm_query` — `prompts: [...]` runs up to 16 children on the fast cheap model concurrently. Read-only. +- **LM fan-out**: `parallel_fanout` — `prompts: [...]` runs up to 16 children on the fast cheap model concurrently. Read-only. - **Other**: `code_execution` (Python sandbox), `validate_data` (JSON/TOML), `request_user_input`, `finance` (market quotes), `tool_search_tool_regex`, `tool_search_tool_bm25` (deferred tool discovery). Multiple `tool_calls` in one turn run in parallel. `web_search` returns `ref_id`s — cite as `(ref_id)`. diff --git a/crates/tui/src/prompts/normal.txt b/crates/tui/src/prompts/normal.txt index 922bf8ec..bd431f53 100644 --- a/crates/tui/src/prompts/normal.txt +++ b/crates/tui/src/prompts/normal.txt @@ -1,6 +1,6 @@ ## Mode: normal -Reads and `rlm_query` run silently. Writes, patches, and shell commands ask for approval. +Reads and `parallel_fanout` run silently. Writes, patches, and shell commands ask for approval. Before requesting writes, use `todo_write` to outline your approach — visible plans build trust. For complex work, layer `update_plan` (strategy) above `todo_write` (tactics). \ No newline at end of file diff --git a/crates/tui/src/tools/mod.rs b/crates/tui/src/tools/mod.rs index e64e323a..863d38d1 100644 --- a/crates/tui/src/tools/mod.rs +++ b/crates/tui/src/tools/mod.rs @@ -14,8 +14,8 @@ pub mod plan; pub mod project; pub mod registry; pub mod review; -pub mod rlm_process; -pub mod rlm_query; +pub mod rlm; +pub mod parallel_fanout; pub mod search; pub mod shell; mod shell_output; diff --git a/crates/tui/src/tools/rlm_query.rs b/crates/tui/src/tools/parallel_fanout.rs similarity index 94% rename from crates/tui/src/tools/rlm_query.rs rename to crates/tui/src/tools/parallel_fanout.rs index f23d9d6b..4216fbc2 100644 --- a/crates/tui/src/tools/rlm_query.rs +++ b/crates/tui/src/tools/parallel_fanout.rs @@ -34,7 +34,7 @@ const MAX_PARALLEL: usize = 16; const DEFAULT_CHILD_TIMEOUT: Duration = Duration::from_secs(120); // --------------------------------------------------------------------------- -// RlmChildClient — dyn-compatible wrapper around LLM completion. +// FanoutChildClient — dyn-compatible wrapper around LLM completion. // // The workspace's `LlmClient` trait uses native `async fn`, which is not dyn // compatible in stable Rust (RPITIT vtable limitations). We define a small @@ -47,13 +47,13 @@ const DEFAULT_CHILD_TIMEOUT: Duration = Duration::from_secs(120); /// operation. `#[async_trait]` desugars the async method into a boxed future /// so the trait is object-safe. #[async_trait] -pub(crate) trait RlmChildClient: Send + Sync { +pub(crate) trait FanoutChildClient: Send + Sync { async fn complete(&self, request: MessageRequest) -> anyhow::Result; } /// Blanket impl: any `DeepSeekClient` is a valid child client. #[async_trait] -impl RlmChildClient for DeepSeekClient { +impl FanoutChildClient for DeepSeekClient { async fn complete(&self, request: MessageRequest) -> anyhow::Result { self.create_message(request).await } @@ -61,28 +61,28 @@ impl RlmChildClient for DeepSeekClient { /// Tool: `rlm_query`. Runs one or more prompts in parallel and joins the /// results. Structured tool call so the model can trigger fan-out reliably. -pub struct RlmQueryTool { - /// Boxed child client — `Arc` lets tests inject a +pub struct ParallelFanoutTool { + /// Boxed child client — `Arc` lets tests inject a /// mock without going through a real HTTP connection. `None` when no API /// key is configured. - client: Option>, + client: Option>, default_model: String, } -impl RlmQueryTool { +impl ParallelFanoutTool { /// Construct with a concrete `DeepSeekClient` (production path). #[must_use] pub fn new(client: Option) -> Self { Self { - client: client.map(|c| Arc::new(c) as Arc), + client: client.map(|c| Arc::new(c) as Arc), default_model: DEFAULT_CHILD_MODEL.to_string(), } } - /// Construct with a pre-boxed `RlmChildClient` — used by tests to inject + /// Construct with a pre-boxed `FanoutChildClient` — used by tests to inject /// a `MockRlmClient` without an active API connection. #[cfg(test)] - pub(crate) fn new_with_arc(client: Option>) -> Self { + pub(crate) fn new_with_arc(client: Option>) -> Self { Self { client, default_model: DEFAULT_CHILD_MODEL.to_string(), @@ -91,9 +91,9 @@ impl RlmQueryTool { } #[async_trait] -impl ToolSpec for RlmQueryTool { +impl ToolSpec for ParallelFanoutTool { fn name(&self) -> &'static str { - "rlm_query" + "parallel_fanout" } fn description(&self) -> &'static str { @@ -178,16 +178,16 @@ impl ToolSpec for RlmQueryTool { }; if prompts.is_empty() { - return Err(ToolError::invalid_input("rlm_query: prompts list is empty")); + return Err(ToolError::invalid_input("parallel_fanout: prompts list is empty")); } if prompts.len() > MAX_PARALLEL { return Err(ToolError::invalid_input(format!( - "rlm_query: too many prompts ({}, max {MAX_PARALLEL})", + "parallel_fanout: too many prompts ({}, max {MAX_PARALLEL})", prompts.len(), ))); } - // client is already Arc — clone the Arc, not the client. + // client is already Arc — clone the Arc, not the client. let model = Arc::new(model); let system = Arc::new(system); let total = prompts.len(); @@ -211,7 +211,7 @@ impl ToolSpec for RlmQueryTool { peak.fetch_max(now, Ordering::Relaxed); debug!( target: "deepseek_cli::tools", - tool = "rlm_query", + tool = "parallel_fanout", idx, in_flight = now, "child request start" @@ -260,7 +260,7 @@ impl ToolSpec for RlmQueryTool { debug!( target: "deepseek_cli::tools", - tool = "rlm_query", + tool = "parallel_fanout", idx, elapsed_ms, ok = response.is_ok(), @@ -277,7 +277,7 @@ impl ToolSpec for RlmQueryTool { let dispatch_elapsed_ms = dispatch_started.elapsed().as_millis() as u64; debug!( target: "deepseek_cli::tools", - tool = "rlm_query", + tool = "parallel_fanout", total, peak = peak.load(Ordering::Relaxed), dispatch_elapsed_ms, @@ -372,8 +372,8 @@ mod tests { ) } - fn tool_without_client() -> RlmQueryTool { - RlmQueryTool::new(None) + fn tool_without_client() -> ParallelFanoutTool { + ParallelFanoutTool::new(None) } // ----------------------------------------------------------------------- @@ -402,7 +402,7 @@ mod tests { } #[async_trait] - impl RlmChildClient for MockRlmClient { + impl FanoutChildClient for MockRlmClient { async fn complete(&self, request: MessageRequest) -> anyhow::Result { // Record start time before sleeping. self.start_times.lock().unwrap().push(Instant::now()); @@ -459,7 +459,7 @@ mod tests { let mock = Arc::new(MockRlmClient::new(delay)); let start_times_ref = Arc::clone(&mock.start_times); - let tool = RlmQueryTool::new_with_arc(Some(mock as Arc)); + let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc)); let prompts: Vec<&str> = vec!["a", "b", "c", "d"]; let overall_start = Instant::now(); @@ -511,7 +511,7 @@ mod tests { #[tokio::test] async fn rlm_single_prompt_returns_plain_text() { let mock = Arc::new(MockRlmClient::new(std::time::Duration::from_millis(1))); - let tool = RlmQueryTool::new_with_arc(Some(mock as Arc)); + let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc)); let result = tool .execute(json!({ "prompt": "hello" }), &ctx()) @@ -527,7 +527,7 @@ mod tests { #[tokio::test] async fn rlm_multi_prompt_returns_indexed_blocks() { let mock = Arc::new(MockRlmClient::new(std::time::Duration::from_millis(1))); - let tool = RlmQueryTool::new_with_arc(Some(mock as Arc)); + let tool = ParallelFanoutTool::new_with_arc(Some(mock as Arc)); let result = tool .execute(json!({ "prompts": ["alpha", "beta"] }), &ctx()) diff --git a/crates/tui/src/tools/registry.rs b/crates/tui/src/tools/registry.rs index 7941d6d4..205037f5 100644 --- a/crates/tui/src/tools/registry.rs +++ b/crates/tui/src/tools/registry.rs @@ -384,18 +384,18 @@ impl ToolRegistryBuilder { /// Include the native RLM tool (`rlm_query`). Parallel/batched LLM /// fan-out runs through the existing DeepSeek client. #[must_use] - pub fn with_rlm_query_tool(self, client: Option) -> Self { - use super::rlm_query::RlmQueryTool; - self.with_tool(Arc::new(RlmQueryTool::new(client))) + pub fn with_parallel_fanout_tool(self, client: Option) -> Self { + use super::parallel_fanout::ParallelFanoutTool; + self.with_tool(Arc::new(ParallelFanoutTool::new(client))) } /// Include the heavy-lift RLM tool (`rlm_process`). Runs the full /// recursive language-model loop on a long input (file or inline /// content); the long input never enters the calling model's context. #[must_use] - pub fn with_rlm_process_tool(self, client: Option, root_model: String) -> Self { - use super::rlm_process::RlmProcessTool; - self.with_tool(Arc::new(RlmProcessTool::new(client, root_model))) + pub fn with_rlm_tool(self, client: Option, root_model: String) -> Self { + use super::rlm::RlmTool; + self.with_tool(Arc::new(RlmTool::new(client, root_model))) } /// Include the review tool. diff --git a/crates/tui/src/tools/rlm_process.rs b/crates/tui/src/tools/rlm.rs similarity index 93% rename from crates/tui/src/tools/rlm_process.rs rename to crates/tui/src/tools/rlm.rs index 192ef765..c4e513c5 100644 --- a/crates/tui/src/tools/rlm_process.rs +++ b/crates/tui/src/tools/rlm.rs @@ -31,7 +31,7 @@ const DEFAULT_MAX_DEPTH: u32 = 1; /// context in the first place. const MAX_INLINE_CONTENT_CHARS: usize = 200_000; -pub struct RlmProcessTool { +pub struct RlmTool { /// Production HTTP client. `None` when no API key is configured. client: Option, /// Root model to drive the RLM loop. Set at registration time; matches @@ -39,7 +39,7 @@ pub struct RlmProcessTool { root_model: String, } -impl RlmProcessTool { +impl RlmTool { #[must_use] pub fn new(client: Option, root_model: String) -> Self { Self { client, root_model } @@ -47,9 +47,9 @@ impl RlmProcessTool { } #[async_trait] -impl ToolSpec for RlmProcessTool { +impl ToolSpec for RlmTool { fn name(&self) -> &'static str { - "rlm_process" + "rlm" } fn description(&self) -> &'static str { @@ -101,7 +101,7 @@ impl ToolSpec for RlmProcessTool { } fn approval_requirement(&self) -> ApprovalRequirement { - // Same level as rlm_query: the model decided to invoke this, the + // Same level as parallel_fanout: the model decided to invoke this, the // user already enabled tools by being in Agent/YOLO mode, and // every concrete side-effect (file read, LLM call) is bounded. ApprovalRequirement::Auto @@ -128,7 +128,7 @@ impl ToolSpec for RlmProcessTool { })? .trim(); if task.is_empty() { - return Err(ToolError::invalid_input("rlm_process: `task` is empty")); + return Err(ToolError::invalid_input("rlm: `task` is empty")); } let file_path = input.get("file_path").and_then(|v| v.as_str()); @@ -137,12 +137,12 @@ impl ToolSpec for RlmProcessTool { let body = match (file_path, content) { (Some(_), Some(_)) => { return Err(ToolError::invalid_input( - "rlm_process: pass `file_path` OR `content`, not both", + "rlm: pass `file_path` OR `content`, not both", )); } (None, None) => { return Err(ToolError::invalid_input( - "rlm_process: requires `file_path` (preferred) or `content`", + "rlm: requires `file_path` (preferred) or `content`", )); } (Some(path), None) => { @@ -156,7 +156,7 @@ impl ToolSpec for RlmProcessTool { (None, Some(c)) => { if c.chars().count() > MAX_INLINE_CONTENT_CHARS { return Err(ToolError::invalid_input(format!( - "rlm_process: inline `content` is {} chars (cap {MAX_INLINE_CONTENT_CHARS}). Pass `file_path` for larger inputs.", + "rlm: inline `content` is {} chars (cap {MAX_INLINE_CONTENT_CHARS}). Pass `file_path` for larger inputs.", c.chars().count() ))); } @@ -166,7 +166,7 @@ impl ToolSpec for RlmProcessTool { if body.trim().is_empty() { return Err(ToolError::invalid_input( - "rlm_process: input is empty after loading", + "rlm: input is empty after loading", )); } @@ -208,7 +208,7 @@ impl ToolSpec for RlmProcessTool { if let Some(err) = result.error { return Err(ToolError::ExecutionFailed { message: format!( - "rlm_process: {err} (iterations={}, termination={:?})", + "rlm: {err} (iterations={}, termination={:?})", result.iterations, result.termination ), }); @@ -217,7 +217,7 @@ impl ToolSpec for RlmProcessTool { if result.answer.trim().is_empty() { return Err(ToolError::ExecutionFailed { message: format!( - "rlm_process: empty answer (termination={:?}, iterations={})", + "rlm: empty answer (termination={:?}, iterations={})", result.termination, result.iterations ), }); @@ -303,8 +303,8 @@ impl ToolSpec for RlmProcessTool { mod tests { use super::*; - fn tool() -> RlmProcessTool { - RlmProcessTool::new(None, "deepseek-v4-pro".to_string()) + fn tool() -> RlmTool { + RlmTool::new(None, "deepseek-v4-pro".to_string()) } fn ctx() -> ToolContext { @@ -321,7 +321,7 @@ mod tests { #[test] fn name_and_schema() { let t = tool(); - assert_eq!(t.name(), "rlm_process"); + assert_eq!(t.name(), "rlm"); let schema = t.input_schema(); assert!(schema["properties"]["task"].is_object()); assert!(schema["properties"]["file_path"].is_object()); @@ -362,7 +362,7 @@ mod tests { #[tokio::test] async fn rejects_missing_task() { - let t = RlmProcessTool::new(None, "x".into()); + let t = RlmTool::new(None, "x".into()); let ctx = ctx(); let res = t .execute(json!({"content": "abc"}), &ctx) diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index b9a5941c..71e3577d 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1927,7 +1927,7 @@ pub enum AppAction { /// Run a Recursive Language Model (RLM) turn — Algorithm 1 from /// Zhang et al. (arXiv:2512.24601). The prompt is stored in the REPL; /// the root LLM only sees metadata. - RlmQuery { + Rlm { /// The user's prompt — stored in REPL, NOT in LLM context. prompt: String, /// Model for the root LLM. diff --git a/crates/tui/src/tui/history.rs b/crates/tui/src/tui/history.rs index 33e102c8..9f90dd0f 100644 --- a/crates/tui/src/tui/history.rs +++ b/crates/tui/src/tui/history.rs @@ -2229,7 +2229,7 @@ mod tests { // its own row instead of the inline `args:` summary so the user can // read what each child was asked. let cell = HistoryCell::Tool(ToolCell::Generic(GenericToolCell { - name: "rlm_query".to_string(), + name: "parallel_fanout".to_string(), status: ToolStatus::Running, input_summary: Some("prompts: <3 items>".to_string()), output: None, diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index c9546b2a..1943ebe5 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -2337,7 +2337,7 @@ async fn apply_command_result( let queued = build_queued_message(app, content); submit_or_steer_message(app, engine_handle, queued).await?; } - AppAction::RlmQuery { + AppAction::Rlm { prompt, model, child_model, @@ -2345,7 +2345,7 @@ async fn apply_command_result( } => { app.status_message = Some("RLM turn starting...".to_string()); let _ = engine_handle - .send(Op::RlmQuery { + .send(Op::Rlm { content: prompt, model, child_model, @@ -4610,7 +4610,7 @@ fn handle_tool_call_started(app: &mut App, id: &str, name: &str, input: &serde_j /// user can read what each child was asked. Returns `None` for tools that /// don't expose a prompt list. fn extract_fanout_prompts(name: &str, input: &serde_json::Value) -> Option> { - if name != "rlm_query" { + if name != "parallel_fanout" { return None; } if let Some(arr) = input.get("prompts").and_then(|v| v.as_array()) { diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index e4a54de5..6adef8b3 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -1862,7 +1862,7 @@ fn rlm_query_tool_cell_wired_with_prompts_on_start() { handle_tool_call_started( &mut app, "rlm-1", - "rlm_query", + "parallel_fanout", &serde_json::json!({ "prompts": [ "What is the capital of France?", @@ -1878,7 +1878,7 @@ fn rlm_query_tool_cell_wired_with_prompts_on_start() { panic!("expected GenericToolCell for rlm_query"); }; - assert_eq!(generic.name, "rlm_query"); + assert_eq!(generic.name, "parallel_fanout"); assert_eq!(generic.status, ToolStatus::Running); // Core assertion: prompts populated from the JSON input. @@ -1902,7 +1902,7 @@ fn rlm_query_singular_prompt_wired_as_single_element_vec() { handle_tool_call_started( &mut app, "rlm-2", - "rlm_query", + "parallel_fanout", &serde_json::json!({ "prompt": "Explain the engine loop" }), ); diff --git a/rlm_catalog.md b/rlm_catalog.md new file mode 100644 index 00000000..500e499d --- /dev/null +++ b/rlm_catalog.md @@ -0,0 +1,99 @@ +# Product Catalog - Winter 2025 + +## Electronics + +### SKU-001: QuantumBook Pro Laptop +- Price: $1,299.99 +- Category: Computing +- Stock: 45 units +- Rating: 4.7/5 +- Features: 16" 4K display, 32GB RAM, 1TB SSD, AI accelerator +- Warranty: 2 years +- Supplier: TechGlobal Inc. + +### SKU-002: SmartWatch X5 +- Price: $349.99 +- Category: Wearables +- Stock: 120 units +- Rating: 4.3/5 +- Features: Heart rate, ECG, GPS, 7-day battery +- Warranty: 1 year +- Supplier: GadgetWorld Ltd. + +### SKU-003: NoiseCancel Pro Headphones +- Price: $199.99 +- Category: Audio +- Stock: 8 units (LOW STOCK) +- Rating: 4.9/5 +- Features: ANC, 40hr battery, spatial audio +- Warranty: 1 year +- Supplier: AudioTech Corp. + +## Home & Kitchen + +### SKU-004: SmartBrew Coffee Maker +- Price: $89.99 +- Category: Kitchen Appliances +- Stock: 200 units +- Rating: 4.5/5 +- Features: App-controlled, 12-cup, thermal carafe +- Warranty: 2 years +- Supplier: HomeEssentials LLC + +### SKU-005: AeroChef Air Fryer +- Price: $129.99 +- Category: Kitchen Appliances +- Stock: 75 units +- Rating: 4.6/5 +- Features: 8qt capacity, 10 presets, dishwasher safe +- Warranty: 1 year +- Supplier: HomeEssentials LLC + +### SKU-006: PureFlow Water Filter +- Price: $49.99 +- Category: Kitchen Accessories +- Stock: 0 units (OUT OF STOCK) +- Rating: 4.4/5 +- Features: 3-stage filtration, 6-month filter life +- Warranty: 1 year +- Supplier: CleanWater Systems + +## Sports & Outdoors + +### SKU-007: TrailBlazer Hiking Boots +- Price: $159.99 +- Category: Footwear +- Stock: 60 units +- Rating: 4.8/5 +- Features: Waterproof, Vibram sole, ankle support +- Warranty: 1 year +- Supplier: OutdoorGear Co. + +### SKU-008: FlexCore Yoga Mat +- Price: $39.99 +- Category: Fitness +- Stock: 300 units +- Rating: 4.2/5 +- Features: 6mm thick, non-slip, carrying strap +- Warranty: 6 months +- Supplier: FitLife Products + +### SKU-009: PowerLift Adjustable Dumbbells +- Price: $299.99 +- Category: Fitness +- Stock: 15 units +- Rating: 4.7/5 +- Features: 5-52.5lbs range, quick-change, storage tray +- Warranty: 2 years +- Supplier: FitLife Products + +## Automotive + +### SKU-010: DashCam Ultra 4K +- Price: $179.99 +- Category: Car Electronics +- Stock: 35 units +- Rating: 4.5/5 +- Features: 4K recording, night vision, GPS, parking mode +- Warranty: 1 year +- Supplier: AutoGadget Inc. diff --git a/rlm_test_doc.md b/rlm_test_doc.md new file mode 100644 index 00000000..7622ab36 --- /dev/null +++ b/rlm_test_doc.md @@ -0,0 +1,51 @@ +This is a test document for rlm_process. + +# Security Model Overview + +The system uses a zero-trust architecture where every request is authenticated and authorized independently. + +## Authentication + +Authentication is handled via JWT tokens with a 15-minute expiry. Refresh tokens are stored in an HTTP-only cookie. + +## Authorization + +Role-based access control (RBAC) is used with three tiers: +- Admin: full access +- Editor: can modify content but not system settings +- Viewer: read-only access + +## Encryption + +All data at rest is encrypted using AES-256-GCM. Data in transit uses TLS 1.3. + +## Audit Logging + +Every action is logged to an append-only audit trail stored in a separate database. + +# API Endpoints + +- GET /api/users - List users (Admin only) +- POST /api/users - Create user (Admin only) +- GET /api/documents - List documents +- POST /api/documents - Create document (Admin, Editor) +- PUT /api/documents/:id - Update document (Admin, Editor) +- DELETE /api/documents/:id - Delete document (Admin only) + +# Data Model + +User { + id: UUID + email: String + role: Enum(Admin, Editor, Viewer) + created_at: Timestamp +} + +Document { + id: UUID + title: String + content: Text + author_id: UUID (FK -> User) + created_at: Timestamp + updated_at: Timestamp +}