Skip to content

feat(iii-directory): skills index and fixes to registry operations - #133

Merged
sergiofilhowz merged 1 commit into
mainfrom
feat/registry-and-skills-index
May 14, 2026
Merged

feat(iii-directory): skills index and fixes to registry operations#133
sergiofilhowz merged 1 commit into
mainfrom
feat/registry-and-skills-index

Conversation

@sergiofilhowz

@sergiofilhowz sergiofilhowz commented May 14, 2026

Copy link
Copy Markdown
Contributor
  • Bump iii-sdk dependency version from 0.11.3 to 0.11.6 in Cargo.toml and update the corresponding checksum in Cargo.lock.
  • Enhance README and skill documentation to reflect changes in skill metadata, including the addition of the type field in skill responses.
  • Introduce a new directory::skills::index function to provide a concise overview of installed workers, improving agent bootstrapping efficiency.
  • Update various skill-related documentation to clarify the structure and usage of skills and prompts, ensuring consistency across the codebase.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added directory::skills::index function for rendering token-light skill indices
    • Skills now support YAML frontmatter for structured metadata (title, type)
    • Registry workers API now uses cursor-based pagination for improved scalability
  • Improvements

    • Skills list and get endpoints now return additional metadata fields
    • Registry worker responses include expanded publication metadata (type, config, dependencies, etc.)
    • Clarified distinction between engine and registry worker introspection surfaces

Review Change Stack

- Bump iii-sdk dependency version from 0.11.3 to 0.11.6 in Cargo.toml and update the corresponding checksum in Cargo.lock.
- Enhance README and skill documentation to reflect changes in skill metadata, including the addition of the `type` field in skill responses.
- Introduce a new `directory::skills::index` function to provide a concise overview of installed workers, improving agent bootstrapping efficiency.
- Update various skill-related documentation to clarify the structure and usage of skills and prompts, ensuring consistency across the codebase.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR evolves the iii-directory service API with enriched skill metadata from YAML frontmatter, adds a new skills index endpoint, refactors registry worker discovery to cursor-based pagination with expanded metadata, updates worker info to fetch from two parallel registry endpoints, and refactors engine introspection handlers to use trigger-based calls instead of direct SDK methods.

Changes

Directory API Evolution

Layer / File(s) Summary
Skill frontmatter parsing foundation
iii-directory/src/fs_source.rs
New SkillFrontmatter struct with optional title and kind (serialized from YAML type key); read_skill_with_frontmatter() function parses markdown YAML blocks, validates non-empty body, and returns frontmatter + stripped body with graceful defaults. Comprehensive unit tests verify extraction, defaults, YAML tolerance, and error handling.
Skill list/get metadata resolution
iii-directory/src/functions/skills.rs
Updated SkillEntry and SkillGetOutput to include optional type field (serde-renamed from kind); new resolve_title() helper implements precedence (frontmatter → H1 → id) and clean_optional() trims and converts empty strings to null; handlers refactored to use frontmatter reader and new helpers.
Skills index endpoint
iii-directory/src/functions/skills.rs
Adds IndexSkillsOutput struct and render_index_markdown() function that filters skills to type: index, renders per-worker markdown sections with titles and descriptions, and generates iii:// read-more links; new endpoint registration and integration tests verify filtering, ordering, description placement, and link generation.
Version/tag query parameter unification
iii-directory/src/sources/registry.rs
VersionSpec::Tag now serializes to ?version= (matching Version); updated documentation and tests reflect unified wire behavior.
Registry workers list cursor pagination
iii-directory/src/functions/registry.rs
WorkerListInput gains opaque cursor and drops limit; new Pagination struct and expanded Worker type with registry metadata (kind/type, config, supported_targets, total_downloads, dependencies, optional image, author); handler builds GET /w?search=&cursor=, response parsing produces { workers, pagination } with tolerant defaults and name-based entry filtering.
Registry workers info dual-endpoint fetching
iii-directory/src/functions/registry.rs
directory::registry::workers::info performs concurrent GET /w/{slug}?version= (detail) and GET /w/{slug}/skills?version= (skills) requests; parse_worker_info_response() now accepts both bodies, merges them into WorkerInfoOutput, unwraps { worker } envelope with legacy fallback, and projects skills to metadata-only SkillsTree view; WorkerInfoSpec adds label() and query_value() helpers for cache-key and wire construction.
Engine introspection trigger-based refactoring
iii-directory/src/functions/directory.rs
New internal engine_list_* helpers (engine_list_functions, engine_list_workers, engine_list_triggers, engine_list_trigger_types) invoke engine via iii.trigger(TriggerRequest), extract response keys, and deserialize with empty-vector fallbacks; handlers for function_info, trigger_info, registered_trigger_list, registered_trigger_info, worker_list, and worker_info refactored to use these wrappers.
API documentation and module docs
iii-directory/README.md, iii-directory/skills/*.md, iii-directory/src/lib.rs
Comprehensive documentation updates: skills list/get now document frontmatter title and type with resolution precedence; new directory::skills::index documented with rendering rules and worked examples; registry vs engine worker surfaces clarified (core fields shared, registry adds publication metadata); workers list documented with cursor pagination and enriched metadata; workers info documents dual-endpoint fetching and merged response.
Feature tests and test step definitions
iii-directory/tests/features/*.feature, iii-directory/tests/steps/*.rs
New and updated Gherkin scenarios verify frontmatter title/type behavior in list/get, skills index filtering and rendering, cursor-paginated registry list with pagination assertions, dual-endpoint registry info responses, version/tag query unification, and body normalization for docstring frontmatter round-tripping. Step definitions extended with pagination assertions and index-specific steps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • iii-hq/workers#124: Refactors the same iii-directory modules (src/fs_source.rs, src/functions/registry.rs, src/functions/directory.rs) to evolve shared skill metadata handling and registry/directory response architecture.
  • iii-hq/workers#131: Directly related refactor toward enriched directory::skills::get metadata and frontmatter-driven title/type resolution in the skill list/get flow.

Suggested reviewers

  • ytallo

🐰 Frontmatter whispers tale of skills so bright,
With types and titles lifting from the night,
An index page collects them all with care,
While cursors page through workers everywhere,
Two fetches dance to make the info whole!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: adding a skills index feature and fixing registry operations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/registry-and-skills-index

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.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 25 skipped (no docs/).

Layer Result
structure
vale
ai

Three for three. Nicely done.

@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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
iii-directory/tests/steps/registry.rs (1)

304-312: ⚡ Quick win

Make the string-specific next_cursor assertion strict.

This step currently converts non-string/null to "", which can mask schema regressions.

Proposed tightening
 fn worker_list_pagination_next_cursor(world: &mut IiiSkillsWorld, expected: String) {
     if world.iii.is_none() {
         return;
     }
     let v = last_ok(world);
-    let actual = v["pagination"]["next_cursor"].as_str().unwrap_or("");
+    let actual = v["pagination"]["next_cursor"]
+        .as_str()
+        .expect("missing pagination.next_cursor string");
     assert_eq!(actual, expected, "pagination: {:?}", v["pagination"]);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/tests/steps/registry.rs` around lines 304 - 312, The test
worker_list_pagination_next_cursor currently masks non-string values by using
as_str().unwrap_or(""), so change it to require a real string: assert that
v["pagination"]["next_cursor"] is a string (e.g. with is_string() or by using
as_str().expect with a clear failure message) and then compare the unwrapped str
to expected; reference the worker_list_pagination_next_cursor function and the
v/last_ok usage so the failure shows the actual JSON type/value instead of
silently turning it into "".
iii-directory/src/functions/directory.rs (2)

147-149: 💤 Low value

Stale SDK version reference in doc comments.

Both doc blocks reference "SDK 0.11.3", but the PR bumps iii-sdk to 0.11.6. Update (or generalize) the comments to avoid future churn:

  • Line 147–149: "SDK 0.11.3 surfaces a single trigger_request_format …"
  • Line 824–826: "no WorkerInfo.trigger_types[] field exists in SDK 0.11.3"

Either re-anchor to 0.11.6 or drop the version pin if the behavior is expected to hold across the 0.11.x line.

Also applies to: 824-826

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/src/functions/directory.rs` around lines 147 - 149, Update the
stale "SDK 0.11.3" references in the doc comments around the
trigger_request_format explanation and the note about
WorkerInfo.trigger_types[]: either change the text to "SDK 0.11.6" or remove the
exact patch-level pin so it reads generically (e.g., "SDK 0.11.x" or no version)
to avoid future churn; edit the doc blocks that mention `trigger_request_format`
and the one referencing `WorkerInfo.trigger_types[]` accordingly in
directory.rs.

720-737: ⚡ Quick win

Sequential engine round-trips where tokio::try_join! would halve latency.

worker_info issues four serial engine_list_* calls (workers, then functions, then trigger_types, then triggers). Each is an independent trigger call to the engine; given typical agent-bootstrap usage where worker_info is called repeatedly, this adds avoidable RTT. After the initial engine_list_workers (whose result drives the not-found check), the remaining three are independent and can be parallelized.

The same applies to registered_trigger_info (lines 642–648) and fetch_functions_and_workers (lines 995–1005).

♻️ Example refactor for `worker_info`
-    let functions = engine_list_functions(iii)
-        .await
-        .map_err(|e| format!("engine::functions::list: {e}"))?;
-    let trigger_types = engine_list_trigger_types(iii, true)
-        .await
-        .map_err(|e| format!("engine::trigger-types::list: {e}"))?;
-    let triggers = engine_list_triggers(iii, true)
-        .await
-        .map_err(|e| format!("engine::triggers::list: {e}"))?;
+    let (functions, trigger_types, triggers) = tokio::try_join!(
+        async {
+            engine_list_functions(iii)
+                .await
+                .map_err(|e| format!("engine::functions::list: {e}"))
+        },
+        async {
+            engine_list_trigger_types(iii, true)
+                .await
+                .map_err(|e| format!("engine::trigger-types::list: {e}"))
+        },
+        async {
+            engine_list_triggers(iii, true)
+                .await
+                .map_err(|e| format!("engine::triggers::list: {e}"))
+        },
+    )?;

Apply the same idea to fetch_functions_and_workers and to the three independent fetches at the top of registered_trigger_info.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/src/functions/directory.rs` around lines 720 - 737, worker_info
currently performs engine_list_functions, engine_list_trigger_types, and
engine_list_triggers sequentially after engine_list_workers; change this to keep
engine_list_workers and the subsequent worker not-found check sequential, then
parallelize the three independent calls using tokio::try_join! (or
futures::try_join!) to await engine_list_functions(iii),
engine_list_trigger_types(iii, true), and engine_list_triggers(iii, true)
concurrently and propagate errors the same way; apply the same pattern to
registered_trigger_info (the three independent trigger fetches) and
fetch_functions_and_workers (the independent function/worker fetches) so each
set of independent engine_list_* calls use try_join! and preserve existing error
formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@iii-directory/src/fs_source.rs`:
- Around line 392-395: The validation for an empty body only trims '\n' so
bodies containing spaces or '\r' slip through; update the logic around
split_frontmatter / fm_text / body / trimmed (and the subsequent error path
using abs_path) to trim all Unicode whitespace (e.g. use body.trim() or
body.trim_matches(|c: char| c.is_whitespace())) before checking is_empty() so
whitespace-only bodies are treated as empty and return the same error.

In `@iii-directory/src/functions/directory.rs`:
- Around line 929-993: The helper functions engine_list_functions,
engine_list_workers, engine_list_triggers, and engine_list_trigger_types are
silently swallowing deserialization errors by using .and_then(...
.ok()).unwrap_or_default(), so change each to explicitly propagate
deserialization errors via Result mapping: fetch the response with
iii.trigger(...).await?, then get the expected field ("functions", "workers",
"triggers", "trigger_types") and convert it using serde_json::from_value(...)
returning Err(IIIError::from or map the serde error) instead of
.ok()/unwrap_or_default(); ensure the function signatures still return
Result<Vec<...>, IIIError> and map serde_json errors into that IIIError so
callers receive a failure rather than an empty Vec for malformed or incompatible
responses (apply the same pattern to engine_list_functions, engine_list_workers,
engine_list_triggers, engine_list_trigger_types).

In `@iii-directory/src/functions/registry.rs`:
- Around line 411-415: The cache key construction using string concatenation
(the cache_key variable in registry.rs and the similar construction around the
second occurrence) can collide when inputs contain ":" or "="; replace the
ad-hoc concatenation with a structured, unambiguous serializer—for example
JSON-serialize or URL-encode the key components or base64-encode a serialized
tuple (e.g., serde_json::to_string(&(search, cursor, other_fields)) or
percent-encode each component) and use that serialized string as the cache key;
update both the cache_key creation sites (the format! call that builds
"worker-list:...") and the analogous key at the other location so keys are safe
from delimiter collisions.

---

Nitpick comments:
In `@iii-directory/src/functions/directory.rs`:
- Around line 147-149: Update the stale "SDK 0.11.3" references in the doc
comments around the trigger_request_format explanation and the note about
WorkerInfo.trigger_types[]: either change the text to "SDK 0.11.6" or remove the
exact patch-level pin so it reads generically (e.g., "SDK 0.11.x" or no version)
to avoid future churn; edit the doc blocks that mention `trigger_request_format`
and the one referencing `WorkerInfo.trigger_types[]` accordingly in
directory.rs.
- Around line 720-737: worker_info currently performs engine_list_functions,
engine_list_trigger_types, and engine_list_triggers sequentially after
engine_list_workers; change this to keep engine_list_workers and the subsequent
worker not-found check sequential, then parallelize the three independent calls
using tokio::try_join! (or futures::try_join!) to await
engine_list_functions(iii), engine_list_trigger_types(iii, true), and
engine_list_triggers(iii, true) concurrently and propagate errors the same way;
apply the same pattern to registered_trigger_info (the three independent trigger
fetches) and fetch_functions_and_workers (the independent function/worker
fetches) so each set of independent engine_list_* calls use try_join! and
preserve existing error formatting.

In `@iii-directory/tests/steps/registry.rs`:
- Around line 304-312: The test worker_list_pagination_next_cursor currently
masks non-string values by using as_str().unwrap_or(""), so change it to require
a real string: assert that v["pagination"]["next_cursor"] is a string (e.g. with
is_string() or by using as_str().expect with a clear failure message) and then
compare the unwrapped str to expected; reference the
worker_list_pagination_next_cursor function and the v/last_ok usage so the
failure shows the actual JSON type/value instead of silently turning it into "".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 01c7e5cd-e3a1-4655-893c-c15c71222a92

📥 Commits

Reviewing files that changed from the base of the PR and between a42eb8e and f6f8b26.

⛔ Files ignored due to path filters (1)
  • iii-directory/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • iii-directory/Cargo.toml
  • iii-directory/README.md
  • iii-directory/skills/directory/registry/workers/info.md
  • iii-directory/skills/directory/registry/workers/list.md
  • iii-directory/skills/directory/skills/get.md
  • iii-directory/skills/directory/skills/index.md
  • iii-directory/skills/directory/skills/list.md
  • iii-directory/skills/index.md
  • iii-directory/src/fs_source.rs
  • iii-directory/src/functions/directory.rs
  • iii-directory/src/functions/mod.rs
  • iii-directory/src/functions/registry.rs
  • iii-directory/src/functions/skills.rs
  • iii-directory/src/lib.rs
  • iii-directory/src/sources/registry.rs
  • iii-directory/tests/features/download_registry.feature
  • iii-directory/tests/features/read.feature
  • iii-directory/tests/features/registry_worker_info.feature
  • iii-directory/tests/features/registry_worker_list.feature
  • iii-directory/tests/steps/download_registry.rs
  • iii-directory/tests/steps/read.rs
  • iii-directory/tests/steps/registry.rs

Comment on lines +392 to 395
let (fm_text, body) = split_frontmatter(&raw);
let trimmed = body.trim_matches('\n');
if trimmed.is_empty() {
return Err(format!("file {} has empty body", abs_path.display()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use whitespace-aware empty-body validation.

The current check only trims \n, so a body with just spaces and/or \r can pass as non-empty after frontmatter stripping.

Suggested fix
-    let trimmed = body.trim_matches('\n');
+    let trimmed = body.trim();
     if trimmed.is_empty() {
         return Err(format!("file {} has empty body", abs_path.display()));
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let (fm_text, body) = split_frontmatter(&raw);
let trimmed = body.trim_matches('\n');
if trimmed.is_empty() {
return Err(format!("file {} has empty body", abs_path.display()));
let (fm_text, body) = split_frontmatter(&raw);
let trimmed = body.trim();
if trimmed.is_empty() {
return Err(format!("file {} has empty body", abs_path.display()));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/src/fs_source.rs` around lines 392 - 395, The validation for an
empty body only trims '\n' so bodies containing spaces or '\r' slip through;
update the logic around split_frontmatter / fm_text / body / trimmed (and the
subsequent error path using abs_path) to trim all Unicode whitespace (e.g. use
body.trim() or body.trim_matches(|c: char| c.is_whitespace())) before checking
is_empty() so whitespace-only bodies are treated as empty and return the same
error.

Comment on lines +929 to +993
async fn engine_list_functions(iii: &III) -> Result<Vec<SdkFunctionInfo>, IIIError> {
let result = iii
.trigger(TriggerRequest {
function_id: "engine::functions::list".into(),
payload: serde_json::json!({}),
action: None,
timeout_ms: None,
})
.await?;
Ok(result
.get("functions")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default())
}

async fn engine_list_workers(iii: &III) -> Result<Vec<WorkerInfo>, IIIError> {
let result = iii
.trigger(TriggerRequest {
function_id: "engine::workers::list".into(),
payload: serde_json::json!({}),
action: None,
timeout_ms: None,
})
.await?;
Ok(result
.get("workers")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default())
}

async fn engine_list_triggers(
iii: &III,
include_internal: bool,
) -> Result<Vec<SdkTriggerInfo>, IIIError> {
let result = iii
.trigger(TriggerRequest {
function_id: "engine::triggers::list".into(),
payload: serde_json::json!({ "include_internal": include_internal }),
action: None,
timeout_ms: None,
})
.await?;
Ok(result
.get("triggers")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default())
}

async fn engine_list_trigger_types(
iii: &III,
include_internal: bool,
) -> Result<Vec<TriggerTypeInfo>, IIIError> {
let result = iii
.trigger(TriggerRequest {
function_id: "engine::trigger-types::list".into(),
payload: serde_json::json!({ "include_internal": include_internal }),
action: None,
timeout_ms: None,
})
.await?;
Ok(result
.get("trigger_types")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect TriggerRequest fields, III::trigger signature, and IIIError variants used in this repo.
fd -t f 'Cargo.toml' | xargs rg -n 'iii-sdk' 
rg -nP '\bpub\s+(struct|enum)\s+IIIError\b' -C 20
rg -nP '\bfn\s+trigger\s*\(' -C 5
rg -nP '\bIIIError::Handler\b' -C 2
rg -nP '\bTriggerRequest\s*\{' -C 8

Repository: iii-hq/workers

Length of output: 50371


🏁 Script executed:

# Check for stale SDK version comments in iii-directory/src/functions/directory.rs
sed -n '147,149p; 824,826p' iii-directory/src/functions/directory.rs

# Check how many handlers call engine_list_* functions
rg -n 'engine_list_' iii-directory/src/functions/directory.rs | head -20

Repository: iii-hq/workers

Length of output: 1484


Silent error swallowing in engine_list_ helpers masks deserialization failures.*

All four engine_list_* helpers use .and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(), which collapses three distinct failure modes into a single empty-Vec result:

  1. Key missing from response → empty Vec.
  2. Malformed/incompatible response shape (e.g., schema drift after SDK update) → empty Vec, silently.
  3. Deserialization error on any element → empty Vec, silently.

These helpers feed critical introspection handlers (trigger_info, registered_trigger_info, worker_info, function_list, etc.). Silent failures surface as "no functions / workers / triggers" with no error signal—defeating introspection's diagnostic purpose. Propagating deserialization errors preserves the IIIError channel and surfaces regressions immediately.

Minor: Lines 147–149 and 152–153 reference "SDK 0.11.3", but Cargo.toml specifies 0.11.6.

Proposed fix: surface deserialization errors instead of swallowing them
 async fn engine_list_functions(iii: &III) -> Result<Vec<SdkFunctionInfo>, IIIError> {
     let result = iii
         .trigger(TriggerRequest {
             function_id: "engine::functions::list".into(),
             payload: serde_json::json!({}),
             action: None,
             timeout_ms: None,
         })
         .await?;
-    Ok(result
-        .get("functions")
-        .and_then(|v| serde_json::from_value(v.clone()).ok())
-        .unwrap_or_default())
+    match result.get("functions") {
+        Some(v) => serde_json::from_value(v.clone()).map_err(|e| {
+            IIIError::Handler(format!("engine::functions::list: invalid `functions` payload: {e}"))
+        }),
+        None => Ok(Vec::new()),
+    }
 }

Apply the same pattern to engine_list_workers, engine_list_triggers, and engine_list_trigger_types, swapping the field name and function id accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/src/functions/directory.rs` around lines 929 - 993, The helper
functions engine_list_functions, engine_list_workers, engine_list_triggers, and
engine_list_trigger_types are silently swallowing deserialization errors by
using .and_then(... .ok()).unwrap_or_default(), so change each to explicitly
propagate deserialization errors via Result mapping: fetch the response with
iii.trigger(...).await?, then get the expected field ("functions", "workers",
"triggers", "trigger_types") and convert it using serde_json::from_value(...)
returning Err(IIIError::from or map the serde error) instead of
.ok()/unwrap_or_default(); ensure the function signatures still return
Result<Vec<...>, IIIError> and map serde_json errors into that IIIError so
callers receive a failure rather than an empty Vec for malformed or incompatible
responses (apply the same pattern to engine_list_functions, engine_list_workers,
engine_list_triggers, engine_list_trigger_types).

Comment on lines +411 to +415
let cache_key = format!(
"worker-list:{}:{}",
search.as_deref().unwrap_or(""),
cursor.as_deref().unwrap_or("")
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use structured cache keys to avoid cross-request collisions.

These keys are delimiter-concatenated strings, so different input tuples can collide when values contain : / =. A collision can return the wrong cached payload for up to registry_cache_ttl_ms.

💡 Suggested fix
-    let cache_key = format!(
-        "worker-list:{}:{}",
-        search.as_deref().unwrap_or(""),
-        cursor.as_deref().unwrap_or("")
-    );
+    let cache_key = format!(
+        "worker-list:{}",
+        serde_json::json!({
+            "search": search.as_deref().unwrap_or(""),
+            "cursor": cursor.as_deref().unwrap_or("")
+        })
+    );
-    let cache_key = format!(
-        "worker-info:{name}:{}={}",
-        spec.label(),
-        spec.query_value()
-    );
+    let cache_key = format!(
+        "worker-info:{}",
+        serde_json::json!({
+            "name": &name,
+            "label": spec.label(),
+            "query": spec.query_value()
+        })
+    );

Also applies to: 465-469

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iii-directory/src/functions/registry.rs` around lines 411 - 415, The cache
key construction using string concatenation (the cache_key variable in
registry.rs and the similar construction around the second occurrence) can
collide when inputs contain ":" or "="; replace the ad-hoc concatenation with a
structured, unambiguous serializer—for example JSON-serialize or URL-encode the
key components or base64-encode a serialized tuple (e.g.,
serde_json::to_string(&(search, cursor, other_fields)) or percent-encode each
component) and use that serialized string as the cache key; update both the
cache_key creation sites (the format! call that builds "worker-list:...") and
the analogous key at the other location so keys are safe from delimiter
collisions.

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