Skip to content

feat(identity-registry): implement Expert Directory enumeration (#25)#28

Merged
Bosun-Josh121 merged 4 commits intoLightForgeHub:mainfrom
KevinMB0220:feat/expert-directory-enumeration
Feb 24, 2026
Merged

feat(identity-registry): implement Expert Directory enumeration (#25)#28
Bosun-Josh121 merged 4 commits intoLightForgeHub:mainfrom
KevinMB0220:feat/expert-directory-enumeration

Conversation

@KevinMB0220
Copy link
Contributor

@KevinMB0220 KevinMB0220 commented Feb 24, 2026

Closes #25

Summary

  • Storage: Added DataKey::VerifiedExpertIndex(u64) and DataKey::TotalVerifiedCount to storage.rs. Implemented add_expert_to_index (appends address at current count, increments counter), get_total_experts, and get_expert_by_index helpers with proper TTL management.
  • Logic: Updated verify_expert and batch_add_experts in contract.rs to call add_expert_to_index only when the expert was not previously Verified, preventing duplicates. Added get_total_experts and get_expert_by_index contract-level wrapper functions.
  • Interface: Exposed get_total_experts(env) -> u64 and get_expert_by_index(env, index: u64) -> Address as public contract methods in lib.rs.
  • Tests: Added 3 new test cases covering enumeration via add_expert, no-duplicate enforcement on re-verification, and enumeration via batch_add_experts. All 20 tests pass.

Design notes

  • TotalVerifiedCount is stored in instance storage (cheap reads, same TTL as contract) since it's a single counter accessed on every write.
  • VerifiedExpertIndex(u64) entries are stored in persistent storage with the same 1-year TTL used for expert records, keeping index and records consistent.
  • The no-duplicate guarantee is structural: the AlreadyVerified guard in both verify_expert and batch_add_experts fires before add_expert_to_index is ever called.

Test plan

  • cargo clippy — no warnings
  • cargo fmt — no changes
  • cargo build — clean
  • cargo test — 20/20 passing
    • test_expert_directory_enumeration — 3 experts added via add_expert, total == 3, indices 0/1/2 correct
    • test_expert_directory_no_duplicates_on_reverify — re-verify returns AlreadyVerified, total stays 1
    • test_expert_directory_via_batch_add — same assertions through batch_add_experts path

Summary by CodeRabbit

  • New Features
    • Expert directory indexing to track and retrieve verified experts
    • New API methods to query total expert count and retrieve experts by index
    • Enhanced admin authorization verification during expert addition and verification processes

Add VerifiedExpertIndex(u64) and TotalVerifiedCount data keys to
support on-chain iteration over verified experts. Implement:
- add_expert_to_index: appends address at current count index,
  increments counter (instance storage), persists index entry with TTL
- get_total_experts: returns total count from instance storage
- get_expert_by_index: retrieves address at a given numeric index

Closes LightForgeHub#25
Call add_expert_to_index in verify_expert and batch_add_experts only
when the expert was not previously Verified, preventing duplicates in
the enumerable directory. Expose get_total_experts and
get_expert_by_index as contract-level functions.

Closes LightForgeHub#25
…ndex

Add two public contract methods to the IdentityRegistryContract
interface so the frontend can enumerate the full verified expert
directory by iterating from 0 to total_experts - 1.

Closes LightForgeHub#25
Three new test cases covering the enumerable expert directory:
- test_expert_directory_enumeration: verifies 3 experts via add_expert,
  asserts total == 3 and correct address at indices 0, 1, 2
- test_expert_directory_no_duplicates_on_reverify: confirms AlreadyVerified
  error and that total stays at 1 when re-verifying the same expert
- test_expert_directory_via_batch_add: same index assertions through the
  batch_add_experts code path

Closes LightForgeHub#25
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

The PR implements an enumerable directory of verified experts by introducing indexed storage for experts, index management functions, and public getters that allow querying total expert count and retrieving experts by sequential index position.

Changes

Cohort / File(s) Summary
Storage Layer
contracts/identity-registry-contract/src/storage.rs
Added VerifiedExpertIndex(u64) and TotalVerifiedCount variants to DataKey enum; implemented add_expert_to_index(), get_total_experts(), and get_expert_by_index() functions to manage expert indexing with TTL extension.
Contract Logic
contracts/identity-registry-contract/src/contract.rs
Integrated storage::add_expert_to_index() calls into verify_expert() and batch_add_experts() to index experts upon verification; added get_total_experts() and get_expert_by_index() public getters.
Public API
contracts/identity-registry-contract/src/lib.rs
Exposed get_total_experts(env) and get_expert_by_index(env, index) methods on IdentityRegistryContract struct, delegating to contract module implementations.
Test Coverage
contracts/identity-registry-contract/src/test.rs
Added three new test cases: test_expert_directory_enumeration (verifies index order and count), test_expert_directory_no_duplicates_on_reverify (ensures re-verification doesn't duplicate entries), and test_expert_directory_via_batch_add (validates batch indexing).

Sequence Diagram

sequenceDiagram
    participant Frontend as Frontend/Client
    participant Contract as Contract Logic
    participant Storage as Storage Layer
    participant Index as Index Store

    Frontend->>Contract: verify_expert(expert_addr)
    Contract->>Storage: Check expert status
    alt Expert not yet verified
        Storage-->>Contract: Status = Pending
        Contract->>Storage: Update status to Verified
        Storage-->>Contract: Status updated
        Contract->>Storage: add_expert_to_index(expert_addr)
        Storage->>Index: Get current total count
        Index-->>Storage: count = N
        Storage->>Index: Store expert at VerifiedExpertIndex(N)
        Storage->>Index: Increment TotalVerifiedCount to N+1
        Index-->>Storage: Index updated
        Storage-->>Contract: Index updated
    else Expert already verified
        Storage-->>Contract: AlreadyVerified error
    end
    Contract-->>Frontend: Success/Error

    Frontend->>Contract: get_total_experts()
    Contract->>Storage: Retrieve TotalVerifiedCount
    Storage->>Index: Fetch count
    Index-->>Storage: count = N
    Storage-->>Contract: N
    Contract-->>Frontend: N

    Frontend->>Contract: get_expert_by_index(index: 0)
    Contract->>Storage: Retrieve expert at index
    Storage->>Index: Fetch VerifiedExpertIndex(0)
    Index-->>Storage: Address
    Storage-->>Contract: Address
    Contract-->>Frontend: Address
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #13: Adds admin authorization and verification checks to verify_expert, which this PR builds upon by integrating expert indexing into the same verification flow.
  • PR #12: Introduces read-only public getters to the contract module; this PR extends the API surface with index-based retrieval getters.
  • PR #9: Modifies expert verification logic and status handling; this PR extends that logic with persistent indexing and enumeration capabilities.

Suggested reviewers

  • Bosun-Josh121

Poem

🐰 A fuzzy indexing quest, we've now begun,
Experts lined up in order, one by one!
No duplicates in sight, all sorted neat,
The directory's complete—expertise compete! 📚✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: implementing expert directory enumeration functionality with a reference to the issue number.
Description check ✅ Passed The pull request description provides comprehensive details including summary of changes, design notes, and test plan. However, it does not follow the template with checkboxes or evidence section, though the substantive content is well-documented.
Linked Issues check ✅ Passed The pull request fully implements all four required objectives from issue #25: storage structures (VerifiedExpertIndex and TotalVerifiedCount), logic updates (verify_expert and batch_add_experts with duplicate prevention), interface exposure (get_total_experts and get_expert_by_index), and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #25 requirements. Minor formatting adjustments (import reordering, spacing) are incidental to the core functionality and do not introduce unrelated features.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
contracts/identity-registry-contract/src/contract.rs (2)

64-92: ⚠️ Potential issue | 🔴 Critical

Banned→Verified re-verification creates a duplicate index entry.

The guard at Line 71 only blocks Verified→Verified. A banned expert can be re-verified, and add_expert_to_index at Line 81 will append them to the index a second time, breaking the uniqueness invariant.

Only index the expert on their first verification (i.e., when transitioning from Unverified):

🐛 Proposed fix
     storage::set_expert_record(env, expert, ExpertStatus::Verified, data_uri);
-    storage::add_expert_to_index(env, expert);
+    if current_status == ExpertStatus::Unverified {
+        storage::add_expert_to_index(env, expert);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/identity-registry-contract/src/contract.rs` around lines 64 - 92,
verify_expert currently only prevents Verified→Verified but will re-add a Banned
expert to the index; update the logic in verify_expert (which calls
storage::get_expert_status, storage::set_expert_record,
storage::add_expert_to_index and events::emit_status_change) so that
storage::add_expert_to_index is only called when the previous status is
ExpertStatus::Unverified (i.e., index the expert only on their first transition
from Unverified→Verified), leaving the Verified check/error intact and keeping
the status update and event emission unchanged.

27-36: ⚠️ Potential issue | 🔴 Critical

Same Banned→Verified duplicate index bug in batch_add_experts.

The guard at Line 29 only blocks Verified status. A Banned expert in the batch will be re-verified and re-indexed. Apply the same fix — only index when transitioning from Unverified.

🐛 Proposed fix
         storage::set_expert_record(&env, &expert, ExpertStatus::Verified, empty_uri);
-        storage::add_expert_to_index(&env, &expert);
+        if status == ExpertStatus::Unverified {
+            storage::add_expert_to_index(&env, &expert);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/identity-registry-contract/src/contract.rs` around lines 27 - 36,
The batch_add_experts loop currently only prevents re-adding Verified experts
but will re-verify and re-index Banned experts; update the logic in the loop
(where storage::get_expert_status, storage::set_expert_record,
storage::add_expert_to_index, and events::emit_status_change are used) to: keep
the early return for status == ExpertStatus::Verified, call
storage::set_expert_record to set Verified as before, but only call
storage::add_expert_to_index when the previous status equals
ExpertStatus::Unverified (i.e., index only on Unverified→Verified transitions);
still call events::emit_status_change with the original status and new
ExpertStatus::Verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/identity-registry-contract/src/storage.rs`:
- Around line 126-131: get_expert_by_index omits the TTL extension performed in
get_expert_record, so index entries can expire while still referenced; to fix,
after retrieving the Address in get_expert_by_index
(DataKey::VerifiedExpertIndex(index)), re-apply the same TTL-extension logic
used by get_expert_record (i.e., use the same persistent storage update/helper
that refreshes the TTL for the key) so the index entry's TTL is extended on each
read and prevents out-of-bounds panics.
- Around line 96-115: The index is append-only and allows duplicates because
re-verifying a previously Banned expert will call add_expert_to_index
unconditionally and get_total_experts returns the append-only count; to fix, add
an indexed-flag or live-count and guard re-indexing: modify add_expert_to_index
to first check a per-address marker (e.g., a new DataKey like
VerifiedIndexed(Address) or change semantics to check current status via
get(&DataKey::ExpertStatus(Address))) and skip appending if already indexed, and
update verify_expert and batch_add_experts to only call add_expert_to_index when
the previous status was Unverified (not when Banned→Verified); also maintain a
separate DataKey::CurrentVerifiedCount (increment on Verified transition,
decrement on Ban transition) and change get_total_experts to return that live
count instead of the append-only TotalVerifiedCount so frontends won’t see
duplicates.

In `@contracts/identity-registry-contract/src/test.rs`:
- Around line 531-556: Add a new unit test that reproduces the Banned→Verified
lifecycle to catch the duplicate-index bug: create env, register
IdentityRegistryContract and client, init with admin, add_expert(&expert, &uri)
and assert get_total_experts() == 1, call ban_expert(&expert), then re-add via
add_expert(&expert, &uri) and assert get_total_experts() remains 1 and that
get_expert_by_index(&0u64) returns the original expert; name the test e.g.
test_expert_directory_no_duplicates_on_reverify_after_ban to mirror the existing
test and exercise the verify→ban→re-verify path.

---

Outside diff comments:
In `@contracts/identity-registry-contract/src/contract.rs`:
- Around line 64-92: verify_expert currently only prevents Verified→Verified but
will re-add a Banned expert to the index; update the logic in verify_expert
(which calls storage::get_expert_status, storage::set_expert_record,
storage::add_expert_to_index and events::emit_status_change) so that
storage::add_expert_to_index is only called when the previous status is
ExpertStatus::Unverified (i.e., index the expert only on their first transition
from Unverified→Verified), leaving the Verified check/error intact and keeping
the status update and event emission unchanged.
- Around line 27-36: The batch_add_experts loop currently only prevents
re-adding Verified experts but will re-verify and re-index Banned experts;
update the logic in the loop (where storage::get_expert_status,
storage::set_expert_record, storage::add_expert_to_index, and
events::emit_status_change are used) to: keep the early return for status ==
ExpertStatus::Verified, call storage::set_expert_record to set Verified as
before, but only call storage::add_expert_to_index when the previous status
equals ExpertStatus::Unverified (i.e., index only on Unverified→Verified
transitions); still call events::emit_status_change with the original status and
new ExpertStatus::Verified.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9463b5 and c7590d4.

📒 Files selected for processing (4)
  • contracts/identity-registry-contract/src/contract.rs
  • contracts/identity-registry-contract/src/lib.rs
  • contracts/identity-registry-contract/src/storage.rs
  • contracts/identity-registry-contract/src/test.rs

Comment on lines +96 to +115
pub fn add_expert_to_index(env: &Env, expert: &Address) {
let count: u64 = env
.storage()
.instance()
.get(&DataKey::TotalVerifiedCount)
.unwrap_or(0u64);

env.storage()
.persistent()
.set(&DataKey::VerifiedExpertIndex(count), expert);
env.storage().persistent().extend_ttl(
&DataKey::VerifiedExpertIndex(count),
LEDGERS_THRESHOLD,
LEDGERS_EXTEND_TO,
);

env.storage()
.instance()
.set(&DataKey::TotalVerifiedCount, &(count + 1));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for relevant state transition and indexing logic
rg -n 'Banned|add_expert_to_index|AlreadyVerified' contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 965


🏁 Script executed:

#!/bin/bash
# Get the verify_expert function with context around line 30-35
sed -n '20,45p' contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1061


🏁 Script executed:

#!/bin/bash
# Get the verify_expert function
rg -B15 -A10 'pub fn verify_expert' contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 961


🏁 Script executed:

#!/bin/bash
# Get the complete verify_expert function
rg -A25 'pub fn verify_expert' contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 862


🏁 Script executed:

#!/bin/bash
# Check if there's any removal/deletion logic for the index in storage.rs
rg -n 'remove|delete|pop|clear' contracts/identity-registry-contract/src/storage.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Double-check by looking at the complete ExpertStatus enum to confirm Banned exists
rg -A5 'enum ExpertStatus' contracts/identity-registry-contract/src/

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 459


Append-only index creates duplicate entries when banned experts are re-verified.

The index has no removal path and is purely append-only. More critically, the verification logic allows the state transition Banned → Verified without preventing re-indexing. Both verify_expert and batch_add_experts only guard against Verified → Verified (checking if current_status == ExpertStatus::Verified), but they do not block Banned → Verified. This means a banned expert can be re-verified, causing add_expert_to_index to append a duplicate entry.

Additionally, get_total_experts returns the total index count (including duplicates), not the count of currently-verified experts, which is misleading.

Frontends iterating the index will need to filter both banned experts and duplicate entries client-side.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/identity-registry-contract/src/storage.rs` around lines 96 - 115,
The index is append-only and allows duplicates because re-verifying a previously
Banned expert will call add_expert_to_index unconditionally and
get_total_experts returns the append-only count; to fix, add an indexed-flag or
live-count and guard re-indexing: modify add_expert_to_index to first check a
per-address marker (e.g., a new DataKey like VerifiedIndexed(Address) or change
semantics to check current status via get(&DataKey::ExpertStatus(Address))) and
skip appending if already indexed, and update verify_expert and
batch_add_experts to only call add_expert_to_index when the previous status was
Unverified (not when Banned→Verified); also maintain a separate
DataKey::CurrentVerifiedCount (increment on Verified transition, decrement on
Ban transition) and change get_total_experts to return that live count instead
of the append-only TotalVerifiedCount so frontends won’t see duplicates.

Comment on lines +126 to +131
pub fn get_expert_by_index(env: &Env, index: u64) -> Address {
env.storage()
.persistent()
.get(&DataKey::VerifiedExpertIndex(index))
.expect("Index out of bounds")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing TTL extension on index entry reads — inconsistent with get_expert_record pattern.

get_expert_record (Line 72-76) extends TTL on reads to keep frequently-accessed entries alive. get_expert_by_index does not, meaning index entries can expire while the count still references them, leading to panics on valid indices.

♻️ Proposed fix
 pub fn get_expert_by_index(env: &Env, index: u64) -> Address {
+    let key = DataKey::VerifiedExpertIndex(index);
+    if env.storage().persistent().has(&key) {
+        env.storage()
+            .persistent()
+            .extend_ttl(&key, LEDGERS_THRESHOLD, LEDGERS_EXTEND_TO);
+    }
     env.storage()
         .persistent()
-        .get(&DataKey::VerifiedExpertIndex(index))
+        .get(&key)
         .expect("Index out of bounds")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/identity-registry-contract/src/storage.rs` around lines 126 - 131,
get_expert_by_index omits the TTL extension performed in get_expert_record, so
index entries can expire while still referenced; to fix, after retrieving the
Address in get_expert_by_index (DataKey::VerifiedExpertIndex(index)), re-apply
the same TTL-extension logic used by get_expert_record (i.e., use the same
persistent storage update/helper that refreshes the TTL for the key) so the
index entry's TTL is extended on each read and prevents out-of-bounds panics.

Comment on lines +531 to +556
#[test]
fn test_expert_directory_no_duplicates_on_reverify() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register(IdentityRegistryContract, ());
let client = IdentityRegistryContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let expert = Address::generate(&env);

client.init(&admin);

let uri = String::from_str(&env, "ipfs://expert");
client.add_expert(&expert, &uri);

// Total is 1
assert_eq!(client.get_total_experts(), 1u64);

// Re-verifying an already verified expert returns AlreadyVerified
let result = client.try_add_expert(&expert, &uri);
assert_eq!(result, Err(Ok(RegistryError::AlreadyVerified)));

// Total remains 1 — no duplicate in the index
assert_eq!(client.get_total_experts(), 1u64);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing test for Banned→Verified re-verification — the scenario that triggers the duplicate index bug.

This test only covers the Verified→Verified path. Add a test that exercises the full lifecycle: verify → ban → re-verify, and asserts that the index count does not increase on re-verification. This is exactly the scenario where the duplicate-index bug identified in contract.rs would surface.

Suggested test
#[test]
fn test_expert_directory_no_duplicates_on_reverify_after_ban() {
    let env = Env::default();
    env.mock_all_auths();

    let contract_id = env.register(IdentityRegistryContract, ());
    let client = IdentityRegistryContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    let expert = Address::generate(&env);

    client.init(&admin);

    let uri = String::from_str(&env, "ipfs://expert");
    client.add_expert(&expert, &uri);
    assert_eq!(client.get_total_experts(), 1u64);

    // Ban the expert
    client.ban_expert(&expert);

    // Re-verify — should NOT create a duplicate index entry
    client.add_expert(&expert, &uri);
    assert_eq!(client.get_total_experts(), 1u64);
    assert_eq!(client.get_expert_by_index(&0u64), expert);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/identity-registry-contract/src/test.rs` around lines 531 - 556, Add
a new unit test that reproduces the Banned→Verified lifecycle to catch the
duplicate-index bug: create env, register IdentityRegistryContract and client,
init with admin, add_expert(&expert, &uri) and assert get_total_experts() == 1,
call ban_expert(&expert), then re-add via add_expert(&expert, &uri) and assert
get_total_experts() remains 1 and that get_expert_by_index(&0u64) returns the
original expert; name the test e.g.
test_expert_directory_no_duplicates_on_reverify_after_ban to mirror the existing
test and exercise the verify→ban→re-verify path.

@Bosun-Josh121 Bosun-Josh121 merged commit 3480c49 into LightForgeHub:main Feb 24, 2026
2 checks passed
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.

Implement "Expert Directory" Enumeration

2 participants