feat(server): entitlement based model (#14996)

#### PR Dependency Tree


* **PR #14996** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Admin mutations to grant/revoke commercial entitlements.
  * New Doc comment-update permission.
  * Realtime user/workspace quota-state endpoints and live-update rooms.

* **Bug Fixes**
  * More accurate readable-doc filtering and permission evaluation.

* **Refactor**
* Workspace feature management moved to entitlement-based model;
permission and quota pipelines redesigned.
  * Admin workspace UI now edits flags only (feature toggles removed).

* **Tests**
* Extensive new and updated tests for permissions, entitlements, quota,
projection, and backfills.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/14996?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-05-19 22:48:05 +08:00
committed by GitHub
parent 103ad2a810
commit c53457691d
150 changed files with 13463 additions and 2458 deletions
@@ -27,6 +27,16 @@ export class MockTeamWorkspace extends Mocker<
quantity,
},
});
await this.db.entitlement.create({
data: {
targetType: 'workspace',
targetId: id,
source: 'cloud_subscription',
plan: 'team',
status: 'active',
quantity,
},
});
await this.db.workspaceFeature.create({
data: {
@@ -45,6 +45,55 @@ export class MockWorkspace extends Mocker<MockWorkspaceInput, MockedWorkspace> {
: undefined,
},
});
const runtimeStateColumns = await this.db.$queryRaw<
Array<{ exists: boolean }>
>`
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'workspace_runtime_states'
AND column_name = 'known'
) AS "exists"
`;
if (runtimeStateColumns[0]?.exists) {
await this.db.$executeRaw`
INSERT INTO workspace_runtime_states (
workspace_id,
known,
readonly,
readonly_reasons,
last_reconciled_at,
stale_after,
updated_at
)
VALUES (${workspace.id}, true, false, ARRAY[]::TEXT[], now(), NULL, now())
ON CONFLICT (workspace_id)
DO UPDATE SET
known = true,
readonly = false,
readonly_reasons = ARRAY[]::TEXT[],
last_reconciled_at = now(),
stale_after = NULL,
updated_at = now()
`;
} else {
await this.db.$executeRaw`
INSERT INTO workspace_runtime_states (
workspace_id,
readonly,
readonly_reasons,
stale_at,
updated_at
)
VALUES (${workspace.id}, false, ARRAY[]::TEXT[], NULL, now())
ON CONFLICT (workspace_id)
DO UPDATE SET
readonly = false,
readonly_reasons = ARRAY[]::TEXT[],
stale_at = NULL,
updated_at = now()
`;
}
// create a rootDoc snapshot
if (snapshot) {