# Permission System

## Overview

The plugin permission system controls access to sensitive resources and APIs. Starting from SDK API version `251212`, all permission declarations are enforced, and plugins that do not declare a valid `sdkapi` are blocked by runtime.

## Introduction

Permissions follow a least-privilege model and pair `permissions` with `permissionReasons` to explain intent to users.

## Quick Start

**1. Declare Permissions in manifest.json**

:::TuffCodeBlock{lang="json"}
---
code: |
  {
  "id": "com.example.plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "sdkapi": 260626,
  "permissions": {
  "required": ["clipboard.read", "network.internet"],
  "optional": ["storage.shared"]
  },
  "permissionReasons": {
  "clipboard.read": "Read clipboard to get text for translation",
  "network.internet": "Access translation API services"
  }
  }
---
:::

**2. Permission Categories**

| Category         | Permission ID         | Risk Level | Description                                  |
| ---------------- | --------------------- | ---------- | -------------------------------------------- |
| **Filesystem**   | `fs.read`             | Medium     | Read user files                              |
|                  | `fs.write`            | High       | Write/modify files                           |
|                  | `fs.execute`          | High       | Execute files or scripts                     |
| **Clipboard**    | `clipboard.read`      | Medium     | Read clipboard content                       |
|                  | `clipboard.write`     | Low        | Write to clipboard                           |
| **Network**      | `network.local`       | Low        | Access local network                         |
|                  | `network.internet`    | Medium     | Access internet                              |
|                  | `network.download`    | Medium     | Download files                               |
| **System**       | `system.shell`        | High       | Execute system commands                      |
|                  | `system.notification` | Low        | Send system notifications                    |
|                  | `system.tray`         | Medium     | Operate system tray                          |
| **Intelligence** | `intelligence.basic`  | Low        | Basic intelligence capabilities              |
|                  | `intelligence.admin`  | High       | Admin intelligence capabilities              |
|                  | `intelligence.agents` | High       | Agent system                                 |
| **Storage**      | `storage.plugin`      | Low        | Plugin private storage (auto-granted)        |
|                  | `storage.shared`      | Medium     | Cross-plugin shared storage                  |
|                  | `storage.sqlite`      | Medium     | Plugin local SQLite database access          |
| **Window**       | `window.create`       | Low        | Create windows (auto-granted)                |
|                  | `window.capture`      | High       | Screen capture                               |
| **Search**       | `search.root-results` | High       | Push or update items in CoreBox root results |

`search.root-results` is the root-result write permission for Search Providers. Any plugin that uses push features or `boxItems.push()` / `plugin.feature.pushItems()` to write content into CoreBox root results must declare and be granted this permission in `manifest.json`; deleting or clearing the plugin's own existing items does not require it, so permission problems do not leave stale results behind.

`window.capture` gates every `context.utils.screenshot` / `context.utils.plugin.screenshot` support, display-list, and capture call. The Screenshot SDK still requires a verified plugin context; declaring the permission does not authorize raw `native:screenshot:*` channel construction. OCR composition additionally requires `intelligence.basic`.

**3. Risk Level Explanation**

- **Low**: Auto-granted or single confirmation
- **Medium**: Requires explicit user authorization
- **High**: Requires double confirmation with warning

**4. Default Auto-granted Permissions**

These permissions are automatically granted:

- `storage.plugin` - Plugin private storage
- `clipboard.write` - Write to clipboard
- `window.create` - Create windows

## SDK Version and Permission Enforcement

**sdkapi Field**

The `sdkapi` field determines whether permission checking can enter the runtime:

| sdkapi Value              | Permission Check | Notes                                                   |
| ------------------------- | ---------------- | ------------------------------------------------------- |
| Not declared              | Blocked          | Runtime returns `SDKAPI_BLOCKED`                        |
| < 251212                  | Blocked          | Runtime returns `SDKAPI_BLOCKED`                        |
| Not in the supported list | Blocked          | Non-canonical or future markers return `SDKAPI_BLOCKED` |
| Supported and >= 251212   | Enabled          | Full permission enforcement                             |

**Migration Guide**

If your plugin doesn't declare `sdkapi` or has a lower version:

:::TuffCodeBlock{lang="json"}
---
code: |
  {
  "sdkapi": 260626,
  "permissions": {
  "required": ["clipboard.read"],
  "optional": []
  }
  }
---
:::

## Technical Notes

- Permission enforcement is managed by the centralized permission center once `sdkapi` meets the threshold.
- Grants are persisted in the SQLite-backed permission store under `<appData>/config/permission/`; legacy `permissions.json` is migration-only and no longer the writable source of truth.

## API Reference

**Check Permissions in Prelude (index.js)**

:::TuffCodeBlock{lang="javascript"}
---
code: |
  const { permission } = globalThis

  // Check if permission is granted
  const hasPermission = await permission.check('clipboard.read')

  // Request permission (triggers user confirmation dialog)
  const granted = await permission.request('clipboard.read', 'Need to read clipboard content')

  if (granted) {
  // Use clipboard API
  const text = clipboard.readText()
  }
---
:::

**Use in Surface (Vue)**

:::TuffCodeBlock{lang="typescript"}
---
code: |
  import { usePermission } from '@talex-touch/utils/plugin/sdk'

  const { check, request, status } = usePermission()

  // Check permission
  const hasClipboard = await check('clipboard.read')

  // Request permission
  const granted = await request('network.internet', 'Need to access translation service')

  // Get all permission status
  const allStatus = await status()
---
:::

**SQLite SDK (sdkapi >= 260215)**

When using `usePluginSqlite()`, declare `storage.sqlite` in required permissions:

:::TuffCodeBlock{lang="json"}
---
code: |
  {
  "sdkapi": 260626,
  "permissions": {
  "required": ["storage.sqlite"],
  "optional": []
  },
  "permissionReasons": {
  "storage.sqlite": "Store plugin business data in the local SQLite database"
  }
  }
---
:::

:::TuffCodeBlock{lang="ts"}
---
code: |
  import { usePluginSqlite } from '@talex-touch/utils/plugin/sdk'
  const sqlite = usePluginSqlite()

  await sqlite.execute(
  'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, created_at TEXT NOT NULL)'
  )

  await sqlite.execute(
  'INSERT INTO notes (title, created_at) VALUES (?, ?)',
  ['hello', new Date().toISOString()]
  )

  const result = await sqlite.query<{ id: number; title: string }>(
  'SELECT id, title FROM notes ORDER BY id DESC LIMIT 10'
  )
  console.log(result.rows)
---
:::

## Best Practices

**1. Principle of Least Privilege**

Only declare permissions you actually need:

:::TuffCodeBlock{lang="json"}
---
code: |
  // ✅ Good
  "permissions": {
  "required": ["clipboard.read"],
  "optional": []
  }

  // ❌ Avoid
  "permissions": {
  "required": ["fs.read", "fs.write", "fs.execute", "system.shell"]
  }
---
:::

**2. Provide Permission Reasons**

:::TuffCodeBlock{lang="json"}
---
code: |
  "permissionReasons": {
  "clipboard.read": "Read text from clipboard for translation",
  "network.internet": "Connect to Google Translate API"
  }
---
:::

**3. Graceful Degradation**

:::TuffCodeBlock{lang="javascript"}
---
code: |
  async function translateText(text) {
  const hasNetwork = await permission.check('network.internet')

      if (!hasNetwork) {
        // Request permission or prompt user
        const granted = await permission.request('network.internet')
        if (!granted) {
          return { error: 'Network permission required for translation' }
        }
      }

      // Normal translation logic
      return await http.post('...')

  ## }
---
:::

**4. Distinguish Required vs Optional**

:::TuffCodeBlock{lang="json"}
---
code: |
  "permissions": {
  "required": ["clipboard.read"], // Core functionality
  "optional": ["network.internet"] // Enhanced features
  }
---
:::

## User Interface

Users can manage plugin permissions at:

1. **Plugin Details > Permissions Tab**: View and manage single plugin permissions
2. **Runtime Dialog**: Shown when plugin first requests permission

## FAQ

**Q: Why does my plugin show `SDKAPI_BLOCKED`?**

A: Your `manifest.json` doesn't declare `sdkapi`, uses an invalid / unsupported marker, or the version is below `251212`. Update it to the current supported marker:

:::TuffCodeBlock{lang="json"}
---
code: |
  "sdkapi": 260626
---
:::

**Q: How to handle permission denial?**

A: Provide degraded experience or clear error message:

:::TuffCodeBlock{lang="javascript"}
---
code: |
  const granted = await permission.request('clipboard.read')
  if (!granted) {
  // Show hint, guide user to manual input or authorize
  feature.pushItems([{
  title: 'Clipboard Permission Required',
  subtitle: 'Please grant permission in plugin settings'
  }])
  }
---
:::

**Q: Where is permission data stored?**

A: Permission grants are stored in the SQLite-backed permission store under `<appData>/config/permission/`; `permissions.json` only exists as a legacy migration source when upgrading old installs.
