Lay the foundation for tenant-level RBAC without enforcing it yet.
Schema, types, middleware lookup, and login-flow shape changes ship now;
RequireRole/route annotations and member-management endpoints follow in
PR 2 and PR 3.
Highlights:
- New tenant_members table (Postgres + SQLite) with (user_id, tenant_id)
unique key and (owner|admin|contributor|viewer) role values. Migration
000043 backfills one row per existing user, marking the earliest-created
active user in each tenant as Owner and the rest as Contributor.
- KnowledgeBase.creator_id and CustomAgent.runnable_by_viewer added so
PR 2 can wire ownership-based and Viewer-runtime checks.
- Auth middleware loads the active TenantMember row, sets a new
TenantRoleContextKey, and falls back via three escape hatches:
cross-tenant superusers (CanAccessAllTenants) get Admin in the target
tenant; orphan tenants (zero members) auto-promote the first authenticating
human to Owner; and when tenant.enable_rbac is false (the v1 default)
unmatched lookups fail open with Admin to preserve current behaviour.
- Register / OIDC provisioning now insert an Owner membership for the
registrant via TenantMemberService.EnsureOwner.
- LoginResponse renamed tenant -> active_tenant and gained a memberships
array. POST /auth/switch-tenant issues a fresh token pair scoped to a
target membership; GET /auth/config exposes auth.registration_mode for
the frontend register-tab gating.
- New config keys: auth.registration_mode (self_serve | invite_only,
defaults to self_serve) and tenant.enable_rbac (defaults to false).
Both have WEKNORA_* env overrides.
- Frontend store persists memberships and exposes currentTenantRole;
Login.vue calls /auth/config and hides the Register tab in invite_only
mode. Go SDK types updated to match the new LoginResponse shape.
Wires KnowledgeBase.VectorStoreID and the ownership-aware retrieve factory
into the user-facing knowledge-base lifecycle:
- POST /knowledge-bases validates the requested vector_store_id against
the caller's tenant scope and the engine registry. New error codes
ErrVectorStoreBindingInvalid (2200) and ErrVectorStoreUnavailable (2201)
distinguish the typed branches without echoing UUIDs to the client.
- GET / POST / PUT / PUT-pin responses embed the bound store's display
metadata (name, source, engine_type, status) without exposing any
connection credentials. Cross-tenant shared KBs receive a suppressed
payload (vector_store_id stripped, source="shared") so operator-chosen
store names cannot be enumerated across tenants.
- POST /knowledge-bases/copy synchronously rejects clones whose target
has a different embedding model or vector store, before the async
clone task is enqueued. The async clone worker re-applies the same
checks for defense in depth.
- DELETE /vector-stores/:id refuses to remove a store with bound KBs,
inside a transaction that row-locks the store on PostgreSQL and
serializes via WAL on SQLite. unregister-from-registry is wrapped in
defer/recover so a panic surfaces as a structured warning instead of
silently leaking a stale engine.
- vector_store_id is immutable after creation. The GORM <-:create tag
blocks every ORM update path; the service-layer DTO omits the field
entirely; a reflection-based regression test catches any future
maintainer who adds it back to either layer.
- Empty-string vector_store_id is normalized to nil at both the create
path and inside SharesStoreWith, so rows persisted by callers that
did not run Normalize first cannot trip false same-store comparisons.
Part of #993. Depends on #994 and #1310.
The in-memory SQLite DDL in `knowledgebase_sqlite_test.go` mirrors the
`knowledge_bases` columns the tests need to insert against. It missed two
columns that the `KnowledgeBase` struct has been carrying for a while:
- `wiki_config` (gorm:"column:wiki_config;type:json")
- `indexing_strategy` (gorm:"column:indexing_strategy;type:json")
Without those columns the `INSERT INTO knowledge_bases (...)` statement
GORM emits fails with:
table knowledge_bases has no column named wiki_config
…which cascades to every TestKnowledgeBase_VectorStoreID_* test
failing on a fresh `go test ./internal/application/repository/`.
Reproduce on main (before this commit):
go test ./internal/application/repository/ -count=1
...
--- FAIL: TestKnowledgeBase_VectorStoreID_Save_Immutable (0.00s)
knowledgebase_sqlite_test.go:91:
Received unexpected error:
table knowledge_bases has no column named wiki_config
FAIL github.com/Tencent/WeKnora/internal/application/repository
After this commit:
ok github.com/Tencent/WeKnora/internal/application/repository
The fix is two `TEXT NULL` column additions to the inline DDL. No
production code, no migration, no schema change — strictly aligning the
test scaffold with the model fields that already exist.