- Frontend: copy app with ReBAC aligned to create_app; canEdit for publish/switch; SubjectSearchUser paging; i18n and routes.
- Backend: user list merges dept subtree with group admins; audit/knowledge/auth/role_access updates; F011 alembic; F006 checkpoint.
- PRD updates; AGENTS.md; ignore local *.msi and /query.
Made-with: Cursor
## Summary
F006 RBAC→ReBAC migration had **two** table/schema-mismatch bugs that
were masked by permissive SQLite test fixtures. Fixing both here, plus a
reusable end-to-end verification harness that catches similar
regressions.
### Bug 1: `role_access` → `roleaccess` table name
SQLModel auto-tablenames `RoleAccess` as `roleaccess` (no underscore).
Migration SQL hardcoded `FROM role_access`, causing startup to fail with
`Table 'bisheng.role_access' doesn't exist`. Test fixture mirrored the
typo (`CREATE TABLE role_access`), so tests passed against the fictional
table while production consistently hit the missing one.
### Bug 2: Step 4 case-sensitivity mismatch
Production MySQL declares:
```
business_type enum('SPACE','CHANNEL')
user_role enum('CREATOR','ADMIN','MEMBER')
```
But `SCM_ROLE_MAPPING` / `SCM_TYPE_MAPPING` use **lowercase** keys.
`.get('CREATOR')` returned `None`, so Step 4 silently skipped every
active space/channel member row. On 114 this meant **19 real membership
records were being dropped** — those users would have lost their access
after migration.
Test fixture uses `VARCHAR(16)` + lowercase test data, which masked the
bug. Fix: `.lower()` before mapping lookup (executor + verify_all both).
Added regression test inserting uppercase enum values to guard against
recurrence.
### Verification harness (`scripts/verify_f006_migration.py`)
Self-contained script that seeds all 9 legacy tables with diverse mock
data covering every code path in F006, then runs `migration → --verify →
reconcile → cleanup`. Designed to be reusable for future migrations.
Mock data covers:
- All 10 non-menu `AccessType` values (1, 3, 5, 6, 7, 8, 9, 10, 11, 12)
- Skip boundaries: `type=99` (WEB_MENU), `role_id=1` (admin), PENDING /
REJECTED SCM status, `is_delete=1` tool, `flow_type=15`, non-numeric
`file_level_path`
- viewer+editor dedup on the same (user, resource)
- 3-level folder hierarchy under knowledge_space → folder → folder →
file
Asserts 47 `must_have` tuples present and 17 `must_not_have` tuples
absent in OpenFGA, plus built-in `--verify` regression == 0.
## Validation on 114
```
Step 1 super_admin: 2 Step 4 SCM: 25 (mock 6 + prod 19)
Step 2 user_group: 13 Step 5 owners: 96
Step 3 role_access: 17 Step 6 folder: 14
Total tuples: 167 --verify regression: 0
must_have 47/47 must_not_have 17/17 OVERALL: PASS
```
## Scope
| File | Change |
|---|---|
| `bisheng/permission/migration/migrate_rbac_to_rebac.py` | 2 SQL table
names (role_access → roleaccess), Step 4 case normalization (executor +
verifier) |
| `test/fixtures/table_definitions.py` | Fixture table name aligned |
| `test/test_f006_permission_migration.py` | 7 `insert_rows` table names
aligned + new `test_uppercase_enum_values` |
| `test/test_infrastructure_smoke.py` | Expected set aligned |
| `scripts/verify_f006_migration.py` | **NEW** — reusable E2E
verification harness |
## Test plan
- [ ] `pytest test/test_f006_permission_migration.py
test/test_infrastructure_smoke.py` passes
- [ ] On a clean environment, backend startup runs F006 without error
and Step 4 tuple count matches active `space_channel_member` rows
- [ ] `scripts/verify_f006_migration.py` returns exit 0 on PASS and
cleans up mock data
Seeding the live 114 MySQL with realistic mock data and running the full
F006 migration surfaced a second latent bug: SCM_ROLE_MAPPING and
SCM_TYPE_MAPPING use lowercase keys (creator/admin/member, space/channel),
but production MySQL stores the columns as ENUM('SPACE','CHANNEL') and
ENUM('CREATOR','ADMIN','MEMBER') — uppercase. Step 4 was silently dropping
every real space/channel member row (19 rows on 114) because .get(role)
returned None and the code fell into the "skip unknown role" branch.
Test fixtures had masked the issue: the SQLite schema uses VARCHAR(16)
with lowercase test data, so .get(role.lower_value) happened to work in
tests but never in production.
Fix:
- migrate_rbac_to_rebac.py step4_space_channel_members: normalize role
and biz_type to lower() before mapping lookup
- migrate_rbac_to_rebac.py verify_all: same normalization on scm_set
(so old-system membership comparison stays case-insensitive)
- test_f006_permission_migration.py: new TestStep4.test_uppercase_enum_values
guarding against regressions by inserting the exact values production's
enum would store
Also adds scripts/verify_f006_migration.py — a reusable end-to-end
verification harness that seeds all 9 legacy tables with diverse mock
data (all 10 AccessType values, 6 SCM role/status combinations, 3-level
folder hierarchy, skip-boundary cases for type=99 / role_id=1 /
PENDING / REJECTED / is_delete=1 / flow_type=15 / non-numeric paths),
runs the migration, invokes --verify mode, reconciles 47 must_have and
17 must_not_have tuples against OpenFGA, and cleans up.
Validation on 114 after the fixes:
Step 1 super_admin: 2 Step 4 SCM: 25 (mock 6 + prod 19)
Step 2 user_group: 13 Step 5 owners: 96
Step 3 role_access: 17 Step 6 folder: 14
Total tuples: 167 --verify regression: 0
must_have 47/47 must_not_have 17/17 OVERALL: PASS
SQLModel defaults RoleAccess.__tablename__ to 'roleaccess' (no underscore),
but the F006 RBAC->ReBAC migration queried 'role_access', causing the
migration to fail at startup with "Table 'bisheng.role_access' doesn't
exist". The test fixture mirrored the same typo, so tests passed against
the fictional table while production consistently hit the missing one.
Why: confirmed against the live 114 schema — tables 'roleaccess',
'userrole', 'usergroup', 'groupresource' all lack underscores; only
'space_channel_member', 'user_tenant', 'user_department', 'failed_tuple'
use underscores via explicit __tablename__ overrides. Migration already
had 'userrole'/'usergroup' correct — only 'role_access' was wrong.
After fix, first execution on 114 wrote 101 tuples (1 super_admin,
8 user_group, 83 resource_owners, 9 folder_hierarchy).
- Department admin: FGA admin check with DB parent-chain fallback
- User groups: public groups manageable by dept admins; member-edit empty hint
- Roles: list_roles uses same admin-dept query as org UI; subtree filter + global read-only
- Role scope full path; hide edit/delete for readonly rows
- Alembic v2_5_0_f010 user name non-unique; client/platform URL and i18n updates
Made-with: Cursor
Expand section 10 of the v2.5 migration plan to serve as the authoritative
upgrade reference for the open-source community:
- OSS vs commercial dataset breakdown (4 vs 7 datasets)
- Separate upgrade paths (1 image for OSS, 2 images for commercial)
- Auto-upgrade mechanism for dashboard_dataset table
- Verification checklist and troubleshooting
- Safe rollback strategy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The knowledge-space permission rollout was functionally working, but a few
adjacent behaviors still looked like separate privileges when they should have
followed existing product semantics. This commit collapses tag management and
retry back into edit-level behavior, keeps chat on top of existing view
permissions, and wires the platform relation-model editor to read the backend
knowledge-space permission template instead of relying only on its local copy.
Constraint: Preserve the agreed product rule that question-answer access follows visibility and maintenance actions follow edit rather than introducing extra permission ids
Rejected: Keep manage_*_tags and retry_file as standalone actions | that would over-model implementation details as product-level privileges
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Prefer reusing established permission ids for adjacent behaviors unless product explicitly wants a separately toggleable capability
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_knowledge_space_chat_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q; frontend platform TS diagnostics for permission.ts and RolesAndPermissions.tsx
Not-tested: Full frontend interaction flow for the platform relation-model editor consuming the backend template endpoint
Knowledge-space runtime authorization still had several coarse-grained holdouts:
tag management and retry flows were effectively edit-tier only, and folder/file
chat paths were not consistently tied to read permissions. This change removes
chat-specific permission ids, makes chat depend on existing view permissions,
and extends action-level permission checks so tags and retry are governed by the
backend canonical knowledge-space template as part of the permission-first
model.
Constraint: Keep question-answer access tied to existing view permissions rather than inventing separate chat privileges
Rejected: Add chat_file/chat_folder permission ids | question-answer access should follow visibility, not introduce a second read model
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Reuse existing view/edit/delete permission ids for adjacent behaviors unless product semantics explicitly require a new action id
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q; src/backend/.venv/bin/python -m py_compile src/backend/bisheng/knowledge/domain/services/knowledge_space_chat_service.py src/backend/bisheng/knowledge/domain/services/knowledge_space_service.py src/backend/bisheng/permission/domain/knowledge_space_permission_template.py src/backend/bisheng/permission/api/endpoints/resource_permission.py
Not-tested: Dedicated chat-service unit tests and full backend suite with manual/env-dependent tests
The runtime and template work was already converging on permission-first
authorization, but the contract was still implicit and easy to regress.
This commit locks the backend-owned knowledge-space permission template in
place, adds the template API for future frontend reuse, and documents in the
service layer that relation-based defaults are legacy compatibility only while
permissions[] is the authoritative runtime source when bindings exist.
Constraint: Keep current backend behavior stable while clarifying the intended permission-first contract
Rejected: Leave the canonical template implied in service code only | future frontend/backend drift would remain likely
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat relation fallback as compatibility code; do not add new knowledge-space actions without updating the canonical backend template
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py -q
Not-tested: Full backend suite and frontend consumption of the new permission template endpoint
Knowledge-space permissions were still split between coarse OpenFGA relation
checks and UI-only relation-model permissions metadata. This change promotes a
backend-owned canonical knowledge-space permission template, exposes it through
an API for future frontend reuse, and makes runtime checks consume relation
bindings plus permission ids so actions such as delete/download/rename are no
longer governed only by can_read/can_edit/can_manage.
The implementation keeps knowledge_space as the top boundary, applies folder and
knowledge_file resource checks for child operations, and revokes child direct
tuples when membership is removed so child resources do not outlive space
membership by default.
Constraint: Preserve existing knowledge-space APIs while making fine-grained permission ids authoritative at runtime
Rejected: Keep permissions[] as UI metadata only | relation models would continue to misrepresent actual runtime behavior
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: New knowledge-space actions must be added to the canonical backend permission template before wiring UI or runtime checks
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q
Not-tested: Full backend suite with external/manual tests and frontend consumers of the new permission template endpoint
- aremove_member: drop department admin tuple when removing member from dept
- department_service: align primary-dept move with admin/member FGA (existing logic)
- OrganizationMemberEditDialog: remove user group and role hint lines (local/synced/affiliate)
- locales: remove unused memberEditUserGroupsHint / memberEditRolesHint keys
Made-with: Cursor
Knowledge-space ReBAC support still stopped at the space itself. Folder and
knowledge_file resources existed in the schema, but runtime create/delete flows
weren't writing parent or owner tuples, which left file-level permissions and
hierarchical inheritance incomplete for newly created items. This initializes
folder/file parent+owner tuples on creation and cleans them up on file/folder
removal, including recursive folder deletes and batch file deletes.
The regression tests extend the knowledge-space service suite to lock the child
resource tuple lifecycle down.
Constraint: Preserve existing knowledge-file/folder CRUD behavior and avoid changing external APIs
Rejected: Leave folder/file tuples to migration only | newly created resources would never participate in ReBAC hierarchy
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Any runtime path that creates or deletes folder/knowledge_file records must update FGA tuples in the same transaction window
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q
Not-tested: End-to-end permission-management flows on folder/file resources via UI
Knowledge-space settings and member listing were still using edit-level
permissions even when the action was really about membership policy. The
square and preview drawer also stranded users in joined, pending, or rejected
states despite the backend already supporting leave, withdraw, and reapply.
This narrows the backend checks to can_manage where appropriate and wires the
existing frontend flows to the available APIs, including space-tag deletion.
Constraint: Reuse the current client-side knowledge-space surfaces and existing APIs without introducing a new permission-management shell
Rejected: Leave joined/pending/rejected actions disabled in the square | backend unsubscribe and resubscribe flows would remain unreachable
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Any UI state that reflects a backend transition should keep an actionable path when the API already supports it
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q; frontend TS diagnostics clean for changed knowledge-space files; locale JSON parse
Not-tested: Browser-interactive client flows and generic permission-management entry points for knowledge space/folder/file
Knowledge-space permissions were still drifting away from member state after
the earlier destructive-operation fixes. Public subscriptions did not write
viewer tuples, visibility changes did not reconcile active members, and
creators could remove their own membership record while remaining the DB owner.
This keeps tuple state and membership state in sync across subscribe,
unsubscribe, and visibility transitions.
The tests extend the focused knowledge-space service coverage so these ReBAC
consistency cases stay guarded without needing the full application stack.
Constraint: Preserve existing knowledge-space API responses and subscription statuses
Rejected: Rely only on approval handlers for tuple repair | public subscriptions and visibility switches would still drift
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Membership status transitions must update FGA tuples in the same code path that mutates SCM records
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py src/backend/test/test_permission_service.py src/backend/test/test_permission_relation_bindings.py src/backend/test/test_knowledge_space_upload_regressions.py -q
Not-tested: End-to-end approval message flows and live share-link behavior
The ReBAC migration wired core relation checks into knowledge spaces, but
several endpoints still trusted caller-supplied space IDs or creator ownership.
This normalizes delete authorization onto can_delete and validates that file
and folder IDs actually belong to the target space before reading, deleting,
or downloading them.
The change also adds focused regression tests for the P0 cases so these
cross-space and missing-permission regressions stay locked down.
Constraint: Keep the current knowledge-space API surface and error types stable
Rejected: Fix only delete_space | cross-space file and folder operations would remain exploitable
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Any endpoint accepting both space_id and file_id/folder_id must verify resource ownership before acting
Tested: src/backend/.venv/bin/pytest src/backend/test/test_knowledge_space_service.py -q
Not-tested: Full backend suite and live MinIO/download integration flows
This note records how knowledge-space permissions actually work today:
coarse-grained relation levels are enforced at runtime, while the
relation-model permissions list is saved and displayed but not yet wired
into per-action checks. The goal is to make the current behavior explicit
before further knowledge-space review and testing.
Constraint: Describe present behavior only and avoid implying a future product decision
Rejected: Leave the explanation only in chat context | it would be easy to lose the current-state understanding during follow-up work
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat this as a current-state note, not a final permission design contract
Tested: Documentation-only change
Not-tested: N/A
When the grantable relation-model API failed, the permission grant UI fell
back to viewer/editor/manager only, which temporarily removed the ability
to grant owner access. This restores owner to the fallback model list and
aligns the shared RelationSelect fallback options with the full built-in
relation set.
Constraint: Preserve the existing fallback flow while keeping the built-in permission levels complete
Rejected: Patch only PermissionGrantTab | other fallback consumers would still present an incomplete relation set
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Fallback relation-model lists should mirror the full built-in relation set unless the backend intentionally restricts them
Tested: npm test -- --run src/test/smoke.test.ts
Not-tested: Interactive permission-grant flow while the relation-model API is unavailable
The role editor was persisting role metadata first and menu permissions in a
second request, which left partial updates behind whenever the second step
failed. This extends the v2 role create/update payloads to carry menu_ids
and handles menu replacement in the same backend transaction as the role
record update. The editor now submits one save request for both pieces of
state.
Constraint: Preserve backward compatibility for callers that still use the separate menu endpoint
Rejected: Frontend-only compensation after menu save failure | still leaves edit updates non-atomic and vulnerable to partial commits
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Persist role metadata and role web-menu access in one transaction whenever they originate from the same form
Tested: pytest src/backend/test/test_role_service.py -q; npm test -- --run src/test/smoke.test.ts
Not-tested: End-to-end role create/edit flows against a live backend
Editing an existing role used to treat a failed or empty menu fetch as the
same thing as the default menu set, which could overwrite the role's saved
menu permissions on the next save. This keeps default menus only for create
mode, tracks menu loading state for edit mode, and disables saving until the
actual menu data has been loaded or reloaded successfully.
Constraint: Preserve create-mode defaults while preventing edit-mode overwrites from incomplete menu state
Rejected: Silently keep the old fallback and rely on users not to save | one transient fetch failure could rewrite role menus incorrectly
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not reuse create-mode defaults as an edit-mode fallback for persisted role settings
Tested: npm test -- --run src/test/smoke.test.ts
Not-tested: Interactive role-edit retry flow in the browser
The user-group edit page initialized its member selection from only the
first 500 members, then saved through a full-replacement sync endpoint.
Large groups could therefore lose members that were never loaded into the
UI. This adds a paginated helper that fetches all member pages before the
edit form builds its selection state, plus a small frontend regression test
for the pagination helper.
Constraint: Keep the existing sync-based save contract while eliminating truncated initialization data
Rejected: Raise the single-request limit only | groups larger than the new cap would still be vulnerable to silent removals
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any UI that performs full-sync saves must hydrate from the full dataset, not a capped first page
Tested: npm test -- --run src/test/userGroups.test.ts; npm test -- --run src/test/smoke.test.ts
Not-tested: Manual editing of a very large user group in the browser
The repository currently has mixed signals around user-group admins between
the top-level PRD, the older F003 spec, and the partial creator-centric UI
and service changes. Since this topic is paused pending a product-level
alignment, the earlier fail-fast rejection of extra admin_user_ids is backed
out to avoid forcing one semantic direction in code.
Constraint: Keep the user-group admin question open without adding further silent behavior changes
Rejected: Leave the rejection in place while the semantics are still under review | it would keep pushing implementation toward one disputed model
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not harden user-group admin semantics further until product direction is explicitly chosen
Tested: pytest src/backend/test/test_user_group_service.py -q; pytest src/backend/test/test_user_group_api.py -q
Not-tested: Legacy clients that submit admin_user_ids during create while semantics remain unresolved
The previous note stated a target conclusion, but this document is meant to
capture the current repository state instead. This rewrites it into a
neutral status report that separates the top-level PRD, the older F003
spec, the current UI, and the mixed backend compatibility state without
choosing the future direction.
Constraint: Keep the note descriptive and avoid turning it into a product decision document
Rejected: Leave the earlier conclusion wording in place | it overstates one possible direction as current truth
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Use this note to describe the present mixed state only; future direction still needs an explicit product decision
Tested: Documentation-only change
Not-tested: N/A
The current branch contains mixed signals between the top-level PRD,
legacy F003 specs, and implementation work around user-group admins.
This note records the working product conclusion for future changes:
user groups should support independent admins just like departments,
while this topic is paused for now to avoid pushing the codebase further
in the wrong direction.
Constraint: Preserve the current pause state and avoid implying that creator-only user groups are the final design
Rejected: Leave the conclusion only in chat context | future code changes would re-open the same ambiguity without a repo-local reference
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat user-group creator and user-group admin as separate concepts in future design work unless product direction changes explicitly
Tested: Documentation-only change
Not-tested: N/A
The create-group API schema still accepted admin_user_ids even though the
new user-group model no longer supports separate group admins. That made the
request succeed while silently discarding extra admin assignments. This now
fails fast when callers submit admins beyond the creator, while still
allowing the creator's own ID for backward compatibility.
Constraint: Keep creator-only creation payloads working while eliminating silent data loss
Rejected: Continue accepting and ignoring extra admin_user_ids | callers cannot tell the request was only partially applied
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If an API field is no longer supported, reject it explicitly rather than silently dropping it
Tested: pytest src/backend/test/test_user_group_service.py -q; pytest src/backend/test/test_user_group_api.py -q
Not-tested: Legacy external clients that may still submit extra admin_user_ids on create
The new user-group service relied on OpenFGA admin tuples only, but several
legacy auth and info paths still identify group admins from the
user_group.is_group_admin rows. That left newly created groups invisible to
those older permission checks. This writes the creator's legacy admin row at
creation time while keeping the FGA admin tuple path in place.
Constraint: Preserve the newer creator-based/FGA model without breaking existing admin-group readers
Rejected: Rewrite every legacy admin lookup in one pass | broader migration with higher regression risk
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Until all legacy auth reads are removed, group creation must keep FGA admin and user_group admin rows aligned
Tested: pytest src/backend/test/test_user_group_service.py -q; pytest src/backend/test/test_user_group_api.py -q
Not-tested: Mixed old/new user-group management flows in a live environment
Relation-model rebinding was treating a same-relation model switch as a
grant plus revoke against the same FGA tuple, which removed the actual
permission. Binding records also dropped include_children scope, so list
revokes and model deletion could revoke broader department grants than the
original authorization. This keeps same-relation model switches binding-only,
persists scoped binding metadata, and forwards department include_children
when modifying or revoking from the permission list.
Constraint: Maintain compatibility with legacy binding keys already stored in config
Rejected: Fix only the frontend request payload | model deletion and backend binding lookups would still over-revoke
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Relation-model metadata must never change FGA tuples unless relation or include_children actually changes
Tested: pytest src/backend/test/test_permission_relation_bindings.py -q; pytest src/backend/test/test_permission_service.py -q
Not-tested: Interactive permission-management UI flow across all resource types
Role management only filtered department admins by subtree in the list view,
while direct detail, update, delete, and menu operations still accepted any
tenant role ID. This adds explicit subtree checks for create and single-role
operations, and marks out-of-scope tenant roles read-only in list responses.
Constraint: Preserve tenant-admin behavior while tightening only department-admin scope
Rejected: Rely on list filtering alone | direct role endpoints remain callable with guessed IDs
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Any single-role endpoint reachable by department admins must re-check subtree ownership server-side
Tested: pytest src/backend/test/test_role_service.py -q
Not-tested: Full role-management UI flow for department-admin accounts
Department member editing was deriving manageable groups from visibility and
then writing membership rows directly, which bypassed the user-group
mutation rules and skipped the existing group change handling path. This
narrows editable groups to the caller's true mutation scope and routes the
membership replacement through UserGroupService so permission checks and
FGA sync stay consistent.
Constraint: Preserve non-manageable memberships while updating only the caller's editable slice
Rejected: Keep direct DAO writes with a narrower visibility filter | still bypasses group service invariants and tuple sync
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Membership writes that affect user_group permissions should go through UserGroupService, not raw DAO diffs
Tested: pytest src/backend/test/test_user_group_service.py -q; pytest src/backend/test/test_user_group_api.py -q
Not-tested: End-to-end organization member edit flow in UI
The member table lost its direct remove action during the edit-dialog
refactor, which left third-party primary members and affiliate members
without any UI path to leave a department. This restores the existing
API-backed removal entry in the table while keeping local-account
delete flows unchanged.
Constraint: Keep the fix UI-only and reuse the existing remove-member API
Rejected: Add remove logic only inside the edit dialog | still leaves key member types without an obvious table action
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Member removal and local-account deletion are distinct flows; preserve both entry points
Tested: Frontend smoke test via vitest smoke suite
Not-tested: Interactive DepartmentPage manual removal flow
HIGH: purge now deletes scoped roles instead of setting department_id=NULL
(prevents privilege escalation to tenant-wide). Blocks purge when archived
children exist (prevents orphan nodes).
HIGH: replace _get_dept_or_raise + _check_permission with combined helper
that returns PermissionDenied for both not-found and no-access (non-admin),
preventing resource existence enumeration.
HIGH: _manageable_group_options now filters to groups where user is admin
or creator, not just visible groups.
MEDIUM: archived departments are now fully read-only — backend rejects
update/set-admins; frontend hides admin and default-role sections.
MEDIUM: fix useCallback stale closure for adminPicks in CreateDepartmentDialog.
LOW: filter archived departments from parent selector and TreeDepartmentSelect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add user_department_infos to telemetry schema, ES mappings, and mid-table sync
- Update UserDao/UserRepositoryImpl to load department info for telemetry
- Add DepartmentDao.get_by_ids and UserDepartmentDao.get_by_user_ids sync methods
- Update 4 Celery sync tasks to include department info
- Add department dimension i18n labels (zh/en/ja)
- Add v2.5 upgrade migration plan for dashboard department dimension
- Include misc docs (architecture, PRD, deployment diagrams) and frontend updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Department: member edit-form/apply-edit, local primary dept change, delete-check and local-account purge
- Register member_router before department_router to fix GET .../members/.../edit-form 404
- Platform: OrganizationMemberEditDialog, MemberTable tooltips, TreeDepartmentSelect, i18n
- Docker: remove broken OpenFGA HEALTHCHECK (distroless has no shell)
- Scripts: default user group removal and sample SQL for secondary dept
Made-with: Cursor
The platform permission UI was importing legacy helpers from
controllers/API/permission.ts, but that module only exported the
ReBAC schema fetcher. Vite then failed at runtime when the page tried
to import getDepartmentTree and related permission helpers.
This change reintroduces the compatibility exports for department tree,
resource permission listing, authorize calls, and permission checks so
existing permission components load again without changing their call
sites.
Constraint: Keep the fix limited to the platform API adapter layer so Drone can redeploy without broader frontend churn
Rejected: Refactor all permission consumers to new import paths | unnecessary for production recovery
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If permission APIs are reorganized again, preserve a compatibility adapter until all platform imports are migrated together
Tested: cd src/frontend/platform && npm test -- --run
Tested: cd src/frontend/platform && npm run build
Not-tested: Browser verification after Drone redeploy
The knowledge-space file queries could crash after successful reprocessing because
ResourceTypeEnum was referenced without being imported, and duplicate detection
continued to treat failed/timeout records as active conflicts. The frontend also
hid the real parse failure details behind generic failed states.
This change restores the query path, excludes failed/timeout records from
duplicate checks, and surfaces backend failure reasons from remark/error_message
in the knowledge-space file views.
Constraint: Keep the fix small and compatible with the current 2.5.0-PM knowledge-space flow
Rejected: Rework upload flow end-to-end | too broad for the production issue at hand
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If duplicate semantics change again, keep failed/timeout records out of active conflict checks unless retry flow is redesigned
Tested: src/backend/.venv/bin/python -m pytest src/backend/test/test_knowledge_space_upload_regressions.py
Not-tested: Live browser verification against the remote server UI
Implement role management on the new role APIs, add department local-member creation flow with assignable roles and synced-readonly rules, and align user group visibility plus ReBAC schema read view to unblock end-to-end org/member setup.
Made-with: Cursor