Replace the default t-dropdown on knowledge document cards with a
custom t-popup-based list that matches the existing card-more popup
styling (rounded corners, blur, dark mode). Selected entries show a
brand-colored check icon.
Also fix the empty placeholder issue: when a document has no tag,
render a dashed "+ category" affordance only for users with edit
permission, instead of an empty tag chip. A "clear category" action
appears at the bottom of the popup only when a tag is already set,
avoiding ambiguity with user-defined tags named the same as the
generic "uncategorized" label.
Adds the knowledgeBase.tagClearAction i18n key in zh-CN, en-US,
ko-KR and ru-RU.
Introduced a new helper function to create a fake Elasticsearch server for testing, improving the reliability of connection probes in vector store tests. Additionally, added methods for setting and listing user-specific knowledge base pins in the knowledge base repository, aligning with recent user pinning features.
Refs: #1303
Modified the AgentQA function to conditionally pass the title disable flag to the executeQA method, enhancing flexibility in session handling. This change improves the behavior of the QA execution based on user requests.
Updated the tenant switch logic to consistently redirect users to the platform's knowledge base list after a tenant switch, simplifying the navigation experience. Removed the previous handling of tenant-scoped routes, ensuring a more predictable behavior during tenant transitions. This change enhances user experience by avoiding potential empty states on reload.
Refs: #1303
Enhanced localization files for English, Korean, Russian, and Chinese by adding the "Shared by me" label to improve clarity in shared resource sections. This update ensures a more intuitive user experience when navigating shared content across different languages.
Refs: #1303
Enhanced English, Korean, Russian, and Chinese localization files by adding new section labels such as "Built-in," "Created by me," and "Other members" to improve user understanding. Adjusted existing labels for better clarity and consistency across languages. This update ensures a more intuitive user experience in navigating shared and personal resources.
Refs: #1303
Refactored the pinning mechanism for knowledge bases to be user-specific, allowing any user with read access to pin knowledge bases independently. This change includes the introduction of a new `user_kb_pins` table to manage pin states, replacing the previous tenant-wide pinning system. Updated relevant API endpoints and service logic to support this new functionality, ensuring that the UI reflects the user's personal pin status.
Additionally, adjusted the knowledge base listing to sort by user-specific pin status, enhancing the user experience by prioritizing pinned items.
Refs: #1303
Refactored the assignment of enableMemoryOverride in the sendMsg function to remove explicit type declaration, enhancing code clarity. This change maintains the existing functionality while streamlining the code structure.
Updated the memory management logic to utilize a server-side per-user preference for enabling memory. The `enable_memory` field is now conditionally set based on user preferences, allowing for better control in both normal and embedded contexts. Adjusted relevant API handlers and request structures to support this change, ensuring backward compatibility with existing clients.
Refs: #1303
Added functionality for managing user preferences, including the ability to update preferences via a new API endpoint. The preferences are now stored server-side, allowing for synchronization across devices. Updated relevant components to handle user preferences, including the UserMenu and settings views. Enhanced internationalization support for error messages related to preference updates.
Introduced a new QuickNav menu item for 'members' in the UserMenu component, allowing users with the appropriate role to access member-related functionalities. Updated the role alignment for QuickNav entries to ensure consistency with the Settings component. This enhancement improves navigation for users based on their permissions.
Refs: #1303
Updated the tenant information API to allow editing of both name and description fields, improving user experience in managing tenant details. Introduced a new GlobalInvitationBell component to display pending invitations in the UI, ensuring users can easily access their invitations without navigating away from their current context. Additionally, updated internationalization files to support new description-related messages.
Refs: #1303
Refactored the Input-field.vue component to enhance the clarity and maintainability of computed properties related to selected items. Improved the logic for handling agent configurations and user-selected files, ensuring a more streamlined approach to displaying knowledge bases and files. Additionally, adjusted the handling of disabled agents to ensure proper fallback selection when necessary.
Refs: #1303
Introduced a new optional field, creator_name, in both CustomAgent and KnowledgeBase types to allow the front end to display the creator's name. This enhancement enables better differentiation between resources created by the current user and those created by other members of the same tenant. Updated relevant API handlers to populate this field during list operations, ensuring accurate representation in the UI. Additionally, modified the ResourceOriginBadge component to utilize the creator_name for improved context in resource listings.
Refs: #1303
Consolidated the template structure in GlobalCommandPalette.vue to enhance code clarity and maintainability. Adjusted the layout of various elements for better alignment without altering existing functionality. This refactor aims to improve the overall readability of the component for future development.
Refs: #1303
Updated the AuditLogRetentionRunner to include a 'started' flag, allowing Stop() to return immediately if Start() was never invoked. This change prevents potential deadlocks during graceful shutdown. Additionally, added tests to ensure that Stop() behaves correctly when called before Start() and when the service is nil.
Refs: #1303
Added functionality for managing pending invitations, including a polling mechanism to update the invitation count in real-time. Introduced a new dialog component for users to view and respond to their invitations without navigating away from their current context. Updated API endpoints for fetching and managing invitations, and enhanced the user interface with relevant internationalization support.
Refs: #1303
Introduced a new "leave workspace" feature with corresponding titles, descriptions, and buttons in English, Korean, Russian, and Chinese. This enhancement improves user experience and clarity in workspace management, allowing users to easily understand the implications of leaving a workspace.
Refs: #1303
Modified chat titles in multiple languages to enhance user engagement. Introduced a new "leave workspace" feature with corresponding titles, descriptions, and buttons in English, Korean, Russian, and Chinese, improving user experience and clarity in workspace management.
Refs: #1303
Enhanced the GetOrganization and ListMembers methods to enforce tenant-based access control. Users can now only access organization details if their tenant is a member or if the organization is marked as searchable. This change prevents unauthorized enumeration of organizations and ensures sensitive member information is only accessible to authorized tenants.
Refs: #1303
Updated the customAgentService to include a new KBShareService interface for handling cross-tenant knowledge base sharing. Implemented logic to group knowledge base IDs by effective tenant, ensuring correct querying of shared KBs. Adjusted the GetSuggestedQuestions method to accommodate these changes, improving the handling of FAQ and document chunks for shared knowledge bases.
Refs: #1303
Implemented a new function to clear tenant-specific data from localStorage when a user switches tenants. This prevents issues with stale data being used in the new tenant context, which could lead to backend errors. The function is called during the tenant selection process to ensure a clean state for the new tenant.
Refs: #1303
Modified padding values in menu.vue and UserMenu.vue to enhance the visual consistency of the components. Reduced padding in various elements and adjusted avatar sizes for a more cohesive design. These changes aim to improve the overall user interface without altering functionality.
Refs: #1303
Introduced a new API client for managing user favorites, allowing users to list, add, and remove starred resources. Enhanced the sidebar to display favorites and recents, improving user navigation. Updated internationalization files to include relevant labels and tooltips for the new features across multiple languages.
Refs: #1303
Consolidated and formatted the template structure in ListSpaceSidebar.vue to enhance code clarity and maintainability. Adjusted the layout of various elements for better alignment without altering existing functionality. This refactor aims to improve the overall readability of the component for future development.
Refs: #1303
Updated the CreateTenantDialog component to improve the header layout by adding an icon and title for better visual clarity. Introduced new CSS styles for the header to enhance the overall user experience. Additionally, adjusted font sizes in UserMenu.vue for consistency across the application.
Refs: #1303
Consolidated template elements in menu.vue to enhance code clarity and maintainability. Adjusted formatting for better alignment and readability without altering existing functionality. This refactor aims to streamline the component's structure for future development.
Refs: #1303
Refactored the CreateTenantDialog component to enhance the user experience by improving the layout and adding a subtitle for clarity. Updated the internationalization files to reflect changes in terminology from "tenant" to "workspace" across multiple languages, ensuring consistency in user messaging. This includes adjustments to prompts and labels related to workspace creation.
Refs: #1303
Added a new configuration option to limit the number of tenants a non-superuser can create via self-service. Introduced a new error type for handling cases where users exceed this limit, returning a 429 status code. Updated the tenant creation and update handlers to enforce this limit and provide appropriate feedback to users. Additionally, refactored the tenant update request structure to ensure only mutable fields are allowed.
Refs: #1303
Added a new API endpoint for creating tenants, allowing any logged-in user to create their own workspace. The backend automatically assigns the creator as the Owner of the new tenant. A new CreateTenantDialog component was introduced in the frontend, enabling users to input the workspace name and an optional description. Internationalization files were updated to include relevant messages for this feature across multiple languages.
Refs: #1303
Refactored the UserMenu.vue component by consolidating template elements and removing unnecessary line breaks. This change enhances the overall readability of the code while maintaining the existing functionality of the user menu.
No functional changes were made; the focus was solely on code clarity.
Refs: #1303
Updated the inline editing input in TenantInfo.vue to ensure proper event handling by consolidating the input attributes. This change enhances the user experience during tenant name editing by maintaining consistent functionality for saving and canceling edits.
Refs: #1303
Implemented a new API endpoint for updating tenant names, allowing owners to edit tenant information directly within the UI. The frontend now supports inline editing with success and error notifications. Additionally, internationalization files have been updated to include relevant messages for the new functionality across English, Korean, Russian, and Chinese languages, ensuring a consistent user experience.
Refs: #1303
Updated internationalization files for English, Korean, Russian, and Chinese to include new messages related to editing tenant names and updating tenant information. This includes prompts for user actions such as saving or canceling changes, as well as success and failure notifications. The changes ensure a consistent user experience across all supported languages regarding tenant management functionalities.
Refs: #1303
Updated the organization membership model to operate at the tenant level, aligning with Plan 3. This change introduces new interfaces and modifies existing ones to accommodate tenant-centric operations, including the `InviteMemberRequest` and `TenantInviteCandidate`. The search functionality for inviting members has been updated to reflect this shift, allowing for tenant-based searches instead of user-based. Additionally, internationalization files have been updated to ensure consistent messaging across languages regarding tenant membership and roles.
Refs: #1303
Enhanced internationalization support by adding RBAC-related messages across multiple language files. These messages inform users about the necessary tenant admin role for actions such as creating, joining, and managing shared spaces. Updated English, Korean, Russian, and Chinese translations to ensure consistent user experience regarding role-based access control.
Refs: #1303
Introduced an index on the created_at column of the audit_logs table to optimize the daily retention sweep, improving performance by reducing the need for full table scans during deletions. This change enhances the efficiency of the retention process, particularly for tenants with high RBAC traffic. The corresponding down migration includes the removal of this index.
Refs: #1303
The audit_logs table grows monotonically until something purges it.
PR 6 explicitly listed retention as out of scope for v1; this picks
that up.
Adds an `audit.retention_days` config (default 90, 0 disables) and a
small background goroutine `AuditLogRetentionRunner` that fires once
~10 minutes after boot and then every 24h, calling
`AuditLogService.Purge` which DELETEs rows older than the cutoff in a
single statement. The dependency surface is intentionally minimal —
no robfig/cron, no asynq, no migration — because retention has no
wall-clock alignment requirement.
Wiring is the same shape as the existing data source scheduler:
container `Provide(NewAuditLogRetentionRunner)`, an `Invoke` that
calls `Start`, and a `ResourceCleaner.RegisterWithName` for graceful
shutdown.
Defaults preserve operator intent:
- `audit:` section omitted from YAML -> retention_days=90.
- explicit `audit.retention_days: 0` in YAML -> purge disabled.
- `WEKNORA_AUDIT_RETENTION_DAYS=N` env override (incl. N=0).
- `retention_days < 0` is rejected by ValidateConfig.
Tests cover the service Purge contract (no-op when disabled, cutoff
math, error propagation) and the runner lifecycle (Start no-op when
disabled, idempotent Start/Stop, ticker cadence, runOnce swallows
errors). 10 new tests, all green.
docs/rbac.md updated with the new YAML / env / behaviour.
Refs: #1303
Plan 3 (#1303) lifted Org membership from per-user to per-tenant, and
40fdb978 pinned the org's owner tenant in DB as organizations.owner_tenant_id.
The OrganizationSettingsModal members list, however, still decided
"which row is the owner" by comparing member.user_id against
orgInfo.owner_id. After Plan 3 the members list is keyed on tenant_id,
so the user-id check is structurally wrong: the representative user of
the owner tenant happens to be the owner today, but if that user is
moved to another tenant — or a different user of the owner tenant
becomes the representative — the modal would either lose the "(owner)"
tag, or re-tag a non-owner row, and would offer the role-select /
remove-button affordances on what is in fact the owner tenant row.
This change:
- Adds OwnerTenantID to OrganizationResponse and populates it in
toOrgResponse from the persisted column. IsOwner now compares the
caller's current tenant against owner_tenant_id (with a user-id
fallback for legacy rows where owner_tenant_id == 0).
- Adds owner_tenant_id to the frontend Organization type and a small
isOwnerMember(member) helper in OrganizationSettingsModal, which
tenant-matches by default and falls back to user-id only on
pre-000046 rows. The role <select>, role tag suffix, and the
delete button on member rows now all use the helper.
The "is-me" CSS class and the me-tag in the member name still match
on user_id — that one really is a per-user marker ("this row's user is
me, the signed-in user").
Plan 3 (#1303) fixed several Org mutating routes (POST /organizations,
/join, /join-request, /join-by-id, /:id/leave, /:id/request-upgrade)
to Admin+ in the caller's tenant, and e1deb810 followed up by gating
DELETE /:id/members/:tenant_id symmetric with /:id/leave.
The remaining Org-admin surfaces still relied solely on a
service-layer "is the caller's tenant admin in this org?" check, with
no route-layer floor. Add g.Admin() to:
- PUT /organizations/:id (update org)
- DELETE /organizations/:id (delete org)
- POST /organizations/:id/invite-code (rotate code)
- GET /organizations/:id/search-users (member picker)
- POST /organizations/:id/invite (direct invite)
- PUT /organizations/:id/members/:tenant_id (change role)
- GET /organizations/:id/join-requests (review queue)
- PUT /organizations/:id/join-requests/:request_id/review
Each of these takes an action that is visible to the whole org or to
another tenant; per the RBAC matrix in router/rbac.go they belong on
Admin+. The service-layer check stays as the authority on org-level
membership; the route guard prevents a tenant Viewer/Contributor from
ever reaching the service in the first place.
Behaviour is unchanged when cfg.Tenant.EnableRBAC is false: RequireRole
continues to log the would-be rejection and let the request through.
Follow-up to the route-level KB-access guard introduced earlier in
this PR. Five reviewer findings, all fixed in middleware + tests
without changing handler call sites:
- Honour `?agent_id` in the agent-share fallback. The in-handler
resolution in knowledgebase.go branches on a specific agent's
KBSelectionMode (all / selected / none); the guard was only ever
calling TenantCanAccessKBViaSomeSharedAgent, which let some
requests pass at the guard layer that the handler would then
reject — and rewrote the request's tenant context to the source
tenant before the rejection happened. The guard now mirrors the
handler: a specific agent_id pins the resolution to that agent
(no fallback to any-agent), an empty agent_id keeps the existing
any-agent behaviour. The knowledge.go / knowledgebase.go handler
helpers stay in place as defence in depth until they're folded
into the guard in a follow-up.
- Distinguish not-found vs. transient on the knowledge / chunk
resolvers. A DB hiccup used to surface as a 404, both confusing
clients and hiding the underlying failure from monitoring. The
resolvers now return apperror 404 only for the known not-found
sentinels (apprepo.ErrKnowledgeBaseNotFound, ErrResourceNotFound)
and let other errors propagate; the guard maps those to a fresh
NewServiceUnavailableError (503).
- Stop 500-ing on legacy chunks with empty knowledge_base_id. The
chunk is effectively unresolvable to a KB, so the client gets the
same 404 they'd get for a missing chunk instead of a 500 that
pollutes alerting. The warn log preserves the operator signal.
- Honour cfg.Tenant.EnableRBAC. The guard now mirrors RequireRole /
RequireOwnershipOrRole: with enforcement off, 401/403 paths log
the would-be rejection and pass through. 404 still fires either
way (a missing resource is not an authorisation event). Keeps the
rollout window safe — operators can flip enforcement on globally
without code changes elsewhere.
- Document the c.Keys vs. c.Request.Context() split. The guard
intentionally rewrites only the request context (handlers that
still run their own share resolution, currently knowledge.go /
knowledgebase.go, would otherwise see kb.TenantID == c.Keys
tenant and misclassify shared-Viewer access as Admin). The
package-level comment now spells this out so the migration of the
remaining handlers in a follow-up doesn't accidentally regress
`my_permission` rendering.
Tests:
- Existing four cases keep working; runGuard signature gained a
guardOpts knob.
- Eight new cases cover the agent-share resolution paths (any-agent
viewer, Editor required → must reject, specific agent in modes
all / selected-match / selected-miss / none, cross-tenant
mismatch) plus the EnableRBAC=false fail-open + 404-still-fires
pair.
Follow-up to the FAQ + Tag refactor in this same PR. Three additional
handler areas now run through the route-level g.KBAccess* guard:
- chunk.go : full refactor — helper effectiveCtxForKnowledge and
validateAndGetChunk's access check are gone; the
handler's chunk-fetch + ownership-mismatch check stay
as defence-in-depth (a same-tenant attacker can't
pass mismatched knowledge_id + chunk_id).
- knowledge.go and knowledgebase.go : routes now run the guard
BEFORE the handler. The in-handler
validateKnowledgeBaseAccess /
resolveKnowledgeAndValidateKBAccess helpers stay for
now because they branch on the agent_id query param
(specific-agent check vs. any-agent fallback) — that
handler-specific branch can collapse into the guard
in a follow-up. The duplicated DB roundtrip is
negligible compared to the handler's main work.
New middleware exports:
- KBIDFromChunkIDParam : walks chunk_id → KnowledgeBaseID via the
chunk's denormalised column.
- g.KBAccessReadFromChunkIDParam / g.KBAccessWriteFromChunkIDParam
: convenience methods on rbacGuards.
Route changes are purely additive (existing role / ownership guards
stay in place; the new KB-access guard runs after them and rewrites
the request's tenant context to the source tenant for shared KBs).
No new dependencies on the kbShareService / agentShareService at the
handler level for chunk.go — those move to the route layer.
Tests pass; behaviour unchanged.
Five handler files used to carry near-identical 30-line helpers
(effectiveCtxForKB / validateAndGetKnowledgeBase) that did the same
own-or-shared resolution before every KB-scoped operation:
1. KB belongs to caller's tenant -> grant own access
2. Org-shared KB -> grant min(share, role) cap
3. Shared agent carries the KB -> grant Viewer (read-only)
A bug found in any one of them had to be fixed in five places, and
several already drifted apart (different log levels, slightly
different error messages, one path missing the share-cap check).
Move the resolution to a route-level gin.HandlerFunc:
middleware.RequireKBAccess, exposed on rbacGuards as
g.KBAccessRead("id") / g.KBAccessWrite("id"). The guard runs after
the role/ownership checks, then on success:
- stashes the resolved (KB + effective tenant id + permission)
on c.Keys under middleware.KBAccessContextKey, and
- rewrites c.Request.Context() to carry the effective tenant id
so handlers downstream just read tenant the way they always did
(types.MustTenantIDFromContext) without a per-handler helper.
Refactor FAQ and Tag handlers to drop their helper methods; their
service constructors lose the kbShareService / agentShareService
dependencies (now lifted to the route layer). Routes:
faq.GET("/entries", g.Viewer(), g.KBAccessRead("id"), ...)
faq.POST("/entries", g.OwnedKBOrAdmin(), g.KBAccessWrite("id"), ...)
kbTags.GET("", g.Viewer(), g.KBAccessRead("id"), ...)
kbTags.POST("", g.OwnedKBOrAdmin(), g.KBAccessWrite("id"), ...)
The middleware also exports KBIDFromKnowledgeIDParam so chunk routes
(URL :knowledge_id) can adopt the same pattern in a follow-up PR
without reshaping the helper.
Five new tests cover own-KB, not-found, shared-with-sufficient-perm,
shared-but-below-min, and missing-tenant-context paths. Handler
tests still pass (no behaviour change).
Plan 3's isOwnerTenant resolved "is this tenant the owner tenant?"
by looking up the owner user's CURRENT tenant via userRepo. That
breaks silently the moment the owner user is moved to another
tenant: the formerly-protected owning tenant becomes removable
(can be kicked from OTM, role-downgraded), and the new tenant the
owner moved to is treated as untouchable even though it isn't even
in OTM.
Promote owner-tenant to a first-class column on `organizations`:
- New migration 000046 adds owner_tenant_id BIGINT NOT NULL with
index. Backfill: (1) owner user's current tenant when known,
(2) earliest Admin tenant in OTM as fallback for orphans, then
(3) RAISE EXCEPTION on anything still NULL — refuse to silently
ship a bogus value. SQLite init schema mirrors the column.
- CreateOrganization now sets OwnerTenantID at create time so the
pinned value is correct for all new orgs.
- isOwnerTenant becomes a pure equality check on the persisted
column — no userRepo lookup, no error tuple. Fail-closed on a
zero column value (legacy data not yet backfilled or unit tests
bypassing the migration) so the worst case is "membership table
frozen for that org" rather than "owner tenant is removable",
matching the prior fail-closed-on-error semantics.
- RemoveTenantMember / UpdateTenantMemberRole simplify to a single
bool check.
Plan 3 of #1303 changed the join-request lookup key from per-user
to per-tenant (GetPendingRequestByTenantAndType), but didn't touch
existing data or add a structural constraint. Result: any (org,
tenant) that had multiple pending rows under the old per-user model
now has ambiguous "which one wins" semantics — the LIMIT 1 query
returns one row, the others stay pending and resurface in admin
lists after the first is approved/rejected.
Two fixes:
1. Migration 000045 up.sql now collapses pre-existing pending rows
to one per (org, tenant, type), keeping the earliest by
created_at. The losers are flagged status='rejected' with a
'[Plan 3] superseded' review_message so the audit trail shows
it was a system cleanup, not a human reject. down.sql drops
the new index but leaves the data mutation in place — we don't
have enough information to safely re-promote the discarded rows.
2. Add a partial unique index on
(organization_id, tenant_id, request_type) WHERE status='pending'
to both the PG migration and the SQLite init schema. This makes
the invariant structural so SubmitJoinRequest /
RequestRoleUpgrade cannot regress.
Approved/rejected history is intentionally unconstrained so the
audit trail remains complete.
Plan 3 of #1303 lifts share ownership to the tenant level, but
RemoveShare / UpdateSharePermission still gated on the original sharer
user. If that user left the tenant or moved on, the source tenant lost
the ability to revoke its own share — only the target org's admin
could clean up, which is governance theatre rather than ownership.
Extend the authz envelope on KB-share and agent-share mutations:
(1) original sharer (unchanged);
(2) source-tenant Admin+ — new, encodes "ownership lives in the
tenant" by reading callerTenantRole from ctx;
(3) target-org admin (unchanged).
Factored out as kbShareService.callerCanManageShare so Update and
Remove cannot drift apart.
Three follow-ups from #1350 review:
1. DELETE /organizations/:id/members/:tenant_id was un-gated, so a
tenant Viewer could remove their own tenant from an org via the
members endpoint and bypass the Admin+ requirement that POST
/:id/leave already enforces. Gate it with g.Admin() — symmetric
with /leave.
2. isOwnerTenant() returned (false, err) when the owner user lookup
failed, and the two call sites only checked the bool. A deleted /
unreadable owner user would silently fall through and let admins
remove or re-role the would-be owner tenant. Make the helper
fail-closed: any lookup error now returns (true, err), and both
call sites refuse the destructive op on error.
3. POST /:id/request-upgrade was un-gated. Approval of the request
changes the whole tenant's org role, so it is in the same trust
tier as join/leave/create. Add g.Admin().