Skip to content

feat(backend): add external KB permission resolver extension point#1025

Open
cocowh wants to merge 5 commits intowecode-ai:mainfrom
cocowh:weagent/kb-permission-extension
Open

feat(backend): add external KB permission resolver extension point#1025
cocowh wants to merge 5 commits intowecode-ai:mainfrom
cocowh:weagent/kb-permission-extension

Conversation

@cocowh
Copy link
Copy Markdown
Contributor

@cocowh cocowh commented Apr 23, 2026

Summary

  • Add IKbPermissionResolver interface and DefaultKbPermissionResolver no-op base in backend/app/services/readers/kb_permissions.py, following the existing groups / group_members reader pattern (ext.wrap(base))
  • get_user_kb_permission() calls kbPermissionResolver as the final fallback after all built-in checks — no behaviour change when SERVICE_EXTENSION is unset (default)
  • list_knowledge_bases() (ALL scope) calls kbPermissionResolver.get_accessible_kb_ids() to include externally-granted KBs in list responses

Motivation

Internal deployments may need to grant KB access based on external systems (e.g. enterprise department membership) without forking core permission logic. This PR introduces the minimal extension points required, keeping all service-specific code isolated in the internal extension package.

Test plan

  • DefaultKbPermissionResolver.resolve always returns None
  • DefaultKbPermissionResolver.get_accessible_kb_ids always returns []
  • _create_resolver wraps with extension when SERVICE_EXTENSION is set
  • Set SERVICE_EXTENSION= (empty) and confirm kbPermissionResolver returns None / []
  • ImportError logs a warning and falls back to the default resolver
  • General exception logs a warning and falls back to the default resolver
  • _LazyReader initialises only once (lazy singleton)
  • get_user_kb_permission calls the external resolver as the last resort
  • Creator check short-circuits before the external resolver is called
  • All 128 existing KB permission tests pass without regression

Summary by CodeRabbit

  • New Features

    • Listings now include knowledge bases made accessible via external permission resolvers; external resolvers are consulted as a final fallback.
  • Behavior

    • Failures in external resolver calls no longer abort listing—errors are logged and listings fall back gracefully.
  • Documentation

    • Updated examples showing how to use the new resolver API.
  • Tests

    • Added tests for default behavior, extension loading/failures, lazy initialization, and integration with access checks.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a pluggable KB permission resolver (lazy-loaded proxy and default impl), integrates it into KB listing and per-user permission checks as a final fallback, updates package exports, and adds tests covering resolver loading and integration.

Changes

Cohort / File(s) Summary
KB Permission Resolver Implementation
backend/app/services/readers/kb_permissions.py
New IKbPermissionResolver interface, DefaultKbPermissionResolver, dynamic _create_resolver() loading via settings.SERVICE_EXTENSION, and a thread-safe lazy proxy exported as kb_permission_resolver. Falls back and logs on import/wrap errors.
Reader Package Export
backend/app/services/readers/__init__.py
Exports kb_permission_resolver in __all__ and updates package usage examples.
Knowledge Service Integration
backend/app/services/knowledge/knowledge_service.py
list_knowledge_bases(..., scope=ResourceScope.ALL) calls kb_permission_resolver.get_accessible_kb_ids(db, user_id) (guarded by try/except with warning) and includes returned external KB IDs in the DB query OR-filter when non-empty.
Share Service Integration
backend/app/services/share/knowledge_share_service.py
KnowledgeShareService.get_user_kb_permission now calls kb_permission_resolver.resolve(db, knowledge_base_id, user_id, kb) as the final fallback; a non-None role authorizes the user (is_creator=False). Docstring updated.
Tests
backend/tests/services/readers/test_kb_permission_resolver.py
New tests for default resolver behavior, extension loading success/failure, _LazyReader lazy-init and delegation, and integration tests ensuring resolver is invoked only as last fallback and skipped for KB creators.

Sequence Diagrams

sequenceDiagram
    actor User
    participant KBS as KnowledgeService
    participant DB as Database
    participant KPR as kb_permission_resolver

    User->>KBS: list_knowledge_bases(ResourceScope.ALL)
    KBS->>DB: Query built-in KB IDs (personal/team/org/shared/bound)
    DB-->>KBS: Built-in KB IDs
    KBS->>KPR: get_accessible_kb_ids(db, user_id)
    KPR-->>KBS: External KB IDs (possibly [])
    KBS->>DB: Apply combined filters including external IDs
    DB-->>KBS: Consolidated KB list
    KBS-->>User: Return filtered KBs
Loading
sequenceDiagram
    actor User
    participant KSS as KnowledgeShareService
    participant DB as Database
    participant KPR as kb_permission_resolver

    User->>KSS: get_user_kb_permission(kb_id, user_id)
    KSS->>DB: Check if user is creator
    alt Creator
        DB-->>KSS: Creator -> ADMIN
        KSS-->>User: Return ADMIN
    else Not Creator
        KSS->>DB: Check explicit ResourceMember
        alt Explicit member found
            DB-->>KSS: Member role
            KSS-->>User: Return role
        else No explicit member
            KSS->>DB: Check group/task bindings
            alt Group/task binding grants role
                DB-->>KSS: Role
                KSS-->>User: Return role
            else No built-in grant
                KSS->>KPR: resolve(db, kb_id, user_id, kb)
                alt External resolver returns role
                    KPR-->>KSS: Role string
                    KSS-->>User: Authorize with resolved role
                else External resolver returns None
                    KSS-->>User: Access Denied
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code and left a trace,
A lazy resolver now finds its place,
External whispers peek and cheer,
KB doors open — then disappear,
Tests and logs hold carrots near.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding an external KB permission resolver extension point. It clearly conveys the feature addition and is directly supported by the PR's implementation of IKbPermissionResolver, DefaultKbPermissionResolver, and the resolver integration points.
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 unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
Contributor

@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: 2

🧹 Nitpick comments (1)
backend/tests/services/readers/test_kb_permission_resolver.py (1)

268-275: Dead assignment: return_value is immediately overridden by side_effect.

Line 268 sets first.return_value = mock_kb, but line 270 then sets first.side_effect = [mock_kb, None]. Since side_effect takes precedence over return_value, line 268 has no effect and can be misleading to readers. Consider removing it.

♻️ Suggested fix
-        # Simulate KB query returning mock_kb
-        mock_query.return_value.filter.return_value.first.return_value = mock_kb
-        # Simulate no explicit permission
-        mock_query.return_value.filter.return_value.first.side_effect = [
+        # KB lookup returns mock_kb, subsequent explicit-permission lookup returns None
+        mock_query.return_value.filter.return_value.first.side_effect = [
             mock_kb,   # KB lookup
             None,      # no explicit ResourceMember
         ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/services/readers/test_kb_permission_resolver.py` around lines
268 - 275, Remove the redundant assignment to
mock_query.return_value.filter.return_value.first.return_value (the line that
sets it to mock_kb) because you immediately override behavior by setting
mock_query.return_value.filter.return_value.first.side_effect = [mock_kb, None];
keep only the side_effect assignment so first() returns mock_kb then None, and
update any test comments to reflect that ResourceMember lookup is simulated via
the side_effect; locate these in the tests in test_kb_permission_resolver.py
around the mock_query and mock_resolver setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/services/readers/test_kb_permission_resolver.py`:
- Around line 319-321: The test unpacks three values from
service.get_user_kb_permission into has_access, role, is_creator but never uses
role, causing a Ruff RUF059 warning; change the unpack to use _role (e.g.,
has_access, _role, is_creator) so the unused variable is clearly marked and the
lint warning is silenced—update the unpack in test_kb_permission_resolver where
get_user_kb_permission is called.
- Around line 77-91: The test currently patches kb_permissions._create_resolver
itself, making it tautological; instead remove the patch.object(...) line and
call the real _create_resolver from the imported module to exercise the
implementation: set mock_settings.SERVICE_EXTENSION = "" (as already done),
re-import app.services.readers.kb_permissions, then call
kb_permissions._create_resolver() and assert isinstance(result,
kb_permissions.DefaultKbPermissionResolver) so the test verifies the
no-extension path of _create_resolver rather than a mocked return.

---

Nitpick comments:
In `@backend/tests/services/readers/test_kb_permission_resolver.py`:
- Around line 268-275: Remove the redundant assignment to
mock_query.return_value.filter.return_value.first.return_value (the line that
sets it to mock_kb) because you immediately override behavior by setting
mock_query.return_value.filter.return_value.first.side_effect = [mock_kb, None];
keep only the side_effect assignment so first() returns mock_kb then None, and
update any test comments to reflect that ResourceMember lookup is simulated via
the side_effect; locate these in the tests in test_kb_permission_resolver.py
around the mock_query and mock_resolver setup.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1269a40b-01d9-4a32-8c88-ab51964ae0ee

📥 Commits

Reviewing files that changed from the base of the PR and between b1b1d20 and ca26aa9.

📒 Files selected for processing (6)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/__init__.py
  • backend/app/services/readers/kb_permissions.py
  • backend/app/services/share/knowledge_share_service.py
  • backend/tests/services/readers/__init__.py
  • backend/tests/services/readers/test_kb_permission_resolver.py

Comment thread backend/tests/services/readers/test_kb_permission_resolver.py
Comment thread backend/tests/services/readers/test_kb_permission_resolver.py Outdated
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

134-148: Silent fallback when wrap() returns a falsy value.

If an extension's wrap(base) returns None (or any falsy value), the loader silently drops it and returns base without any log, making misconfigured extensions hard to diagnose. Either log a warning or use an explicit is None check.

♻️ Suggested tweak
-        ext = importlib.import_module(f"{settings.SERVICE_EXTENSION}.kb_permissions")
-        result = ext.wrap(base)
-        if result:
-            logger.info("KB permission resolver extension loaded")
-            return result
+        ext = importlib.import_module(f"{settings.SERVICE_EXTENSION}.kb_permissions")
+        result = ext.wrap(base)
+        if result is None:
+            logger.warning(
+                "kb_permissions extension wrap() returned None; using default resolver"
+            )
+        else:
+            logger.info("KB permission resolver extension loaded")
+            return result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/readers/kb_permissions.py` around lines 134 - 148, The
loader currently treats any falsy return from ext.wrap(base) as a success and
silently falls back to base; update the logic in the kb_permissions
extension-loading block to detect falsy returns from ext.wrap(base) (preferably
checking "is None" if None is the intended sentinel) and log a warning
(including the extension name via settings.SERVICE_EXTENSION) when ext.wrap
returns None or another unexpected falsy value, before returning base; keep the
existing ImportError/Exception handlers and only consider the ext.wrap return
value for this additional diagnostic logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 423-427: Wrap the external extension call to
kb_permission_resolver.get_accessible_kb_ids in list_knowledge_bases (in
knowledge_service.py) in a try/except so any runtime exception from the
extension is caught, logged via the module logger (include the exception
details), and ext_kb_ids is set to an empty list to allow built-in KB listing to
continue; apply the same defensive pattern around kb_permission_resolver.resolve
calls in knowledge_share_service.py so resolver exceptions degrade gracefully
instead of propagating.

---

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 134-148: The loader currently treats any falsy return from
ext.wrap(base) as a success and silently falls back to base; update the logic in
the kb_permissions extension-loading block to detect falsy returns from
ext.wrap(base) (preferably checking "is None" if None is the intended sentinel)
and log a warning (including the extension name via settings.SERVICE_EXTENSION)
when ext.wrap returns None or another unexpected falsy value, before returning
base; keep the existing ImportError/Exception handlers and only consider the
ext.wrap return value for this additional diagnostic logging.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0884b28-d214-453a-9161-e96654e8f580

📥 Commits

Reviewing files that changed from the base of the PR and between ca26aa9 and 7cdfc82.

📒 Files selected for processing (5)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/__init__.py
  • backend/app/services/readers/kb_permissions.py
  • backend/app/services/share/knowledge_share_service.py
  • backend/tests/services/readers/test_kb_permission_resolver.py
✅ Files skipped from review due to trivial changes (1)
  • backend/app/services/readers/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/services/share/knowledge_share_service.py

Comment thread backend/app/services/knowledge/knowledge_service.py Outdated
Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

161-172: Lazy singleton initialization is not thread-safe.

_get() performs a check-then-set on self._instance without a lock. Under concurrent first access (e.g., two request threads hitting list_knowledge_bases and get_user_kb_permission simultaneously at startup), _create_resolver() can run more than once — which also re-imports the extension module and re-logs the "extension loaded" message. In practice importlib.import_module is cached so the extra cost is small, but it's worth a threading.Lock (double-checked locking) or eager initialization at import time to make the "called only once" guarantee that the tests assert actually hold in production.

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

In `@backend/app/services/readers/kb_permissions.py` around lines 161 - 172, The
lazy singleton _LazyReader is not thread-safe: _get performs a check-then-set on
_instance without synchronization, so concurrent first accesses can call
_create_resolver() multiple times; fix by adding a threading.Lock as a
class/member (e.g., _init_lock) and implement double-checked locking inside _get
(check _instance, acquire lock, check again, call _create_resolver and assign to
_instance) so only one resolver is created, or alternatively perform eager
initialization by calling _create_resolver() at module import and initializing
_instance once; ensure references in __getattr__ still call _get() and preserve
existing symbols (_LazyReader, _get, _instance, _create_resolver, __getattr__).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 428-435: The except block in list_knowledge_bases references an
undefined logger causing a NameError if
kb_permission_resolver.get_accessible_kb_ids(db, user_id) raises; add a
module-level logger so the defensive handler works: import logging at the top
and define logger = logging.getLogger(__name__) (so the except can call
logger.warning), or alternatively replace the except's logger usage with a local
logging.getLogger(__name__).warning call; reference:
kb_permission_resolver.get_accessible_kb_ids and the except in
list_knowledge_bases.

---

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 161-172: The lazy singleton _LazyReader is not thread-safe: _get
performs a check-then-set on _instance without synchronization, so concurrent
first accesses can call _create_resolver() multiple times; fix by adding a
threading.Lock as a class/member (e.g., _init_lock) and implement double-checked
locking inside _get (check _instance, acquire lock, check again, call
_create_resolver and assign to _instance) so only one resolver is created, or
alternatively perform eager initialization by calling _create_resolver() at
module import and initializing _instance once; ensure references in __getattr__
still call _get() and preserve existing symbols (_LazyReader, _get, _instance,
_create_resolver, __getattr__).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23cf7564-f9f8-4149-84e4-4f23f2aa9ce5

📥 Commits

Reviewing files that changed from the base of the PR and between 7cdfc82 and dadae3b.

📒 Files selected for processing (3)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/kb_permissions.py
  • backend/tests/services/readers/test_kb_permission_resolver.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/services/readers/test_kb_permission_resolver.py

Comment thread backend/app/services/knowledge/knowledge_service.py
Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
backend/app/services/readers/kb_permissions.py (1)

135-154: Consider narrowing the fallback error log to include exc_info.

except Exception as e correctly catches the miscellaneous failure modes (missing wrap, wrap raising, etc.), but the warning at line 152 only serializes str(e), which often loses the traceback for non-ImportError failures originating inside the extension. For an extension-loading seam that is otherwise silent, passing exc_info=True on that branch makes diagnosing misbehaving extensions considerably easier without changing behavior.

🛠️ Suggested tweak
-    except Exception as e:
-        logger.warning(f"Failed to load kb_permissions extension: {e}")
+    except Exception as e:  # noqa: BLE001 - extension isolation boundary
+        logger.warning(
+            "Failed to load kb_permissions extension: %s", e, exc_info=True
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/readers/kb_permissions.py` around lines 135 - 154, The
catch-all exception handler that logs "Failed to load kb_permissions extension:
{e}" should include the full traceback for easier debugging; in the except
Exception block that handles failures from importing or calling
settings.SERVICE_EXTENSION.kb_permissions.wrap, change the logger.warning call
that currently serializes only str(e) to pass exc_info=True (keeping the same
message) so the logger records the exception stack trace when wrap() or module
import fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/services/readers/kb_permissions.py`:
- Around line 135-154: The catch-all exception handler that logs "Failed to load
kb_permissions extension: {e}" should include the full traceback for easier
debugging; in the except Exception block that handles failures from importing or
calling settings.SERVICE_EXTENSION.kb_permissions.wrap, change the
logger.warning call that currently serializes only str(e) to pass exc_info=True
(keeping the same message) so the logger records the exception stack trace when
wrap() or module import fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5705820f-989c-4623-856b-1fef5456037f

📥 Commits

Reviewing files that changed from the base of the PR and between dadae3b and 35b3961.

📒 Files selected for processing (2)
  • backend/app/services/knowledge/knowledge_service.py
  • backend/app/services/readers/kb_permissions.py

Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
backend/app/services/knowledge/knowledge_service.py (1)

430-437: Silence Ruff BLE001 with an explanatory noqa.

The broad except Exception here is intentional — the whole point is to prevent a faulty extension from taking down the core listing path — but Ruff will keep flagging it (BLE001). Consider adding a # noqa: BLE001 with a short rationale so the intent is explicit and the lint stays clean.

✏️ Suggested diff
             try:
                 ext_kb_ids = kb_permission_resolver.get_accessible_kb_ids(db, user_id)
-            except Exception as e:
+            except Exception as e:  # noqa: BLE001 - never let extension break core listing
                 logger.warning(
                     f"kb_permissions extension get_accessible_kb_ids failed: {e}; "
                     "falling back to empty list"
                 )
                 ext_kb_ids = []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/knowledge/knowledge_service.py` around lines 430 - 437,
Add a targeted `# noqa: BLE001` to the broad except block that wraps the call to
kb_permission_resolver.get_accessible_kb_ids so Ruff stops flagging the
intentional catch; update the except line in the try/except that assigns
ext_kb_ids (the block that logs via logger.warning and falls back to ext_kb_ids
= []) to include `# noqa: BLE001` and a short inline rationale (e.g.,
"intentional broad except to isolate faulty extension"), keeping the rest of the
handler (logger.warning and fallback to empty list) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/services/knowledge/knowledge_service.py`:
- Around line 430-437: Add a targeted `# noqa: BLE001` to the broad except block
that wraps the call to kb_permission_resolver.get_accessible_kb_ids so Ruff
stops flagging the intentional catch; update the except line in the try/except
that assigns ext_kb_ids (the block that logs via logger.warning and falls back
to ext_kb_ids = []) to include `# noqa: BLE001` and a short inline rationale
(e.g., "intentional broad except to isolate faulty extension"), keeping the rest
of the handler (logger.warning and fallback to empty list) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80c0af4b-55d9-408d-9fc0-134d8959eb0d

📥 Commits

Reviewing files that changed from the base of the PR and between 35b3961 and 29a9fa6.

📒 Files selected for processing (1)
  • backend/app/services/knowledge/knowledge_service.py

Introduce IKbPermissionResolver interface and DefaultKbPermissionResolver
no-op base in backend/app/services/readers/kb_permissions.py, following
the existing groups/group_members reader pattern (ext.wrap(base)).

- get_user_kb_permission() calls kb_permission_resolver as the final
  fallback after all built-in checks — no behaviour change when
  SERVICE_EXTENSION is unset (default)
- list_knowledge_bases() (ALL scope) calls
  kb_permission_resolver.get_accessible_kb_ids() to include
  externally-granted KBs in list responses
- _LazyReader uses double-checked locking (threading.Lock) to guarantee
  the singleton is initialised exactly once under concurrent access
- Defensive try/except guards both call sites so a faulty extension
  cannot break core listing or permission checks
- Module-level logger added to knowledge_service.py; kb_permission_resolver
  imported at module level (no circular dependency)
- Full test suite: default resolver, extension loading, ImportError/exception
  fallback, lazy singleton, and integration with get_user_kb_permission
@cocowh cocowh force-pushed the weagent/kb-permission-extension branch from 29a9fa6 to 2db0887 Compare April 23, 2026 20:19
cocowh added 2 commits April 27, 2026 12:13
- Add exc_info=True to exception logging in _create_resolver()
- Add noqa: BLE001 comment to document intentional broad exception catch
- Improves debugging capability when extension loading fails
Resolved conflicts:
- backend/app/services/knowledge/knowledge_service.py (import statements)
…sion systems

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
cocowh added a commit to cocowh/Wegent that referenced this pull request Apr 28, 2026
…sion systems

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
…sion systems (#27)

Add ExtensionTabConfig interface and KbPermissionsPanel component to support
custom permission tabs (e.g., ERP department permissions) in the knowledge
base permission management UI.

Changes:
- New KbPermissionsPanel component with extensionTabs prop
- Updated KnowledgeDetailPanel to accept permissionExtensionTabs
- Exported ExtensionTabConfig type from knowledge module
- Added English and Chinese documentation

Internal projects can now inject custom permission tabs without modifying
core components, following the same pattern as backend IKbPermissionResolver.

Refs: PR wecode-ai#1025 backend extension point
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