# https://github.com/nduckmink/arkon 项目说明书

生成时间：2026-07-05 18:33:01 UTC

## 目录

- [Arkon 总览与产品定位](#page-1)
- [系统架构与代码组织](#page-2)
- [MRP 管线、Wiki 起草与修订工作流](#page-3)
- [部门作用域、全局作用域与 RBAC 权限引擎](#page-4)
- [MCP 服务器、OAuth 2.1 与 Claude 接入](#page-5)
- [AI Provider 目录、模型选型与嵌入迁移](#page-6)
- [前端 Wiki 浏览、面板与编辑器](#page-7)
- [部署、迁移、运维与已知故障模式](#page-8)

<a id='page-1'></a>

## Arkon 总览与产品定位

### 相关页面

相关主题：[系统架构与代码组织](#page-2), [MRP 管线、Wiki 起草与修订工作流](#page-3)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/nduckmink/arkon/blob/main/README.md)
- [CHANGELOG.md](https://github.com/nduckmink/arkon/blob/main/CHANGELOG.md)
- [DESIGN.md](https://github.com/nduckmink/arkon/blob/main/DESIGN.md)
- [docker-compose.yml](https://github.com/nduckmink/arkon/blob/main/docker-compose.yml)
- [app/main.py](https://github.com/nduckmink/arkon/blob/main/app/main.py)
- [app/database/models.py](https://github.com/nduckmink/arkon/blob/main/app/database/models.py)
</details>

# Arkon 总览与产品定位

## 一、什么是 Arkon

Arkon 是一款面向企业团队的知识管理与 AI 协作平台。它将文档管理（Documents）、结构化知识库（Wiki）以及工作空间（Workspaces）整合到统一系统中，并在此基础上提供与 Claude、Gemini 等大语言模型对接的能力，使团队能够将私有知识作为上下文提供给 LLM，从而构建内部专属的 AI 助手与"技能（Skills）"。

根据社区描述，Arkon 的核心使用场景包括：

- **企业知识沉淀**：上传文档后自动抽取全文并建立索引，供后续问答与 Wiki 生成使用。
- **跨部门协作**：通过 Workspace（v0.9.0 起合并了原 Project 层级）与 Department 维度管理团队成员与权限。
- **AI 技能复用**：用户可在 Arkon 中训练并保存面向 Claude 的 Skill 集合，并在受控范围内分发给内部员工使用。

资料来源：[README.md:1-40]()

## 二、核心功能模块

Arkon 的产品边界可拆解为以下五个相互配合的模块：

| 模块 | 主要职责 | 关键对象 |
| --- | --- | --- |
| Workspaces | 团队协作与数据隔离单元（v0.9.0 整合自原 Projects） | Workspace、Member、Role |
| Documents | 文档上传、解析、全文索引 | Source、Document、full_text |
| Wiki | 基于 Source 自动/手动生成结构化页面 | Page、page_type、sources 关联 |
| Access Control (Scopes) | 跨部门、跨工作空间的细粒度权限 | Scope、Action、ScopeMembership、ScopeRole |
| AI / Skills | 与 Claude、Gemini 集成，托管内部 Skill | MCP 配置、Skill 资产 |

资料来源：[CHANGELOG.md:1-30]()

### 文档与 Wiki 的关系

文档（Document）经抽取后生成 Source，Wiki 页面（Page）可引用一个或多个 Source 作为生成依据。这种设计允许同一份原始材料被多次复用为不同角度的 Wiki 页面。社区反馈显示，部分用户曾因所有 Source 被汇聚到同一个页面而造成归属不清（参见 issue #9），这也促使产品对 Source 与 Page 的关联关系做了进一步澄清。

资料来源：[DESIGN.md:1-50]()

## 三、技术架构概览

Arkon 采用前后端分离的部署形态，整体架构如下：

```mermaid
flowchart LR
  UI[前端 Web UI] -->|HTTP/JSON| API[FastAPI 后端]
  API --> DB[(PostgreSQL)]
  API --> LLM[Claude / Gemini API]
  API --> MCP[MCP Server]
  Docker[docker-compose] --> API
  Docker --> DB
```

- **后端**：基于 FastAPI 构建，数据库访问使用 SQLAlchemy 异步栈（`asyncpg` 驱动），主入口负责注册所有业务路由。
- **数据库**：默认使用 PostgreSQL，承载用户、Workspace、Document、Source、Wiki Page、Scope 等核心表。
- **LLM 集成**：通过 API Key 调用 Claude 或 Gemini，完成文档摘要、Wiki 生成、Skill 推理等任务。
- **MCP**：Arkon 客户端可通过 MCP 与外部工具（如 Revit 工具链）通信，扩展 AI 的实际操作能力（参见 issue #7）。

资料来源：[app/main.py:1-30]()，[docker-compose.yml:1-30]()

## 四、典型用户场景与已知局限

### 典型场景

1. **多部门权限隔离**：管理员可在 Arkon 中创建 Department（Product / Sales / FIN 等），并通过 Scopes 将不同用户绑定到不同部门的可见 Wiki 与可写文档集（参见 issue #11）。
2. **Skill 托管与防扩散**：管理员将训练好的 Claude Skill 上传到 Arkon，仅授权内部成员调用，从而降低核心资产外泄风险（参见 issue #5）。
3. **多语言 Wiki**：用户上传包含多语言内容的文档后，希望按指定主语言生成统一风格的 Wiki 页面（参见 issue #15）。

### 已知局限与社区关注点

- **LLM 供应商绑定**：目前主要支持 Claude 与 Gemini，社区希望引入 Self-hosted 模型（如 Gemma4 / Qwen）以降低对外部 API 的依赖（参见 issue #19）。
- **数据完整性边界**：PostgreSQL 不允许 `0x00` 出现在 `text` / `varchar` / JSON 字段中，因此含 NUL 字节的文档在入库时会触发 `CharacterNotInRepertoireError`（参见 issue #21）。
- **ORM 与迁移一致性**：历史上曾因 `page_type` 缺少 setter、scopes router 未注册等导致 500 / 404 错误（参见 issue #8、#10、#20），这表明 Arkon 在快速演进过程中需要持续维护迁移脚本与路由装配的一致性。

资料来源：[app/database/models.py:1-50]()，[CHANGELOG.md:30-80]()

## 五、版本演进与产品定位

从 v0.9.0 的"BIG Update"开始，Arkon 明确了"以 Workspace 为唯一数据与安全边界"的产品定位，淘汰了旧的 Project 抽象，使权限模型更加扁平、可解释。v0.9.1 则集中修复了若干 UX 与 API 层面的 Minor Bug，体现了产品在"功能收敛 + 体验打磨"阶段的双线推进。

综合来看，Arkon 的产品定位可以概括为：**面向中小型团队的企业级私有知识中枢——通过统一的文档 / Wiki / Workspace / Access Control 体系，将企业内部知识资产安全地交付给 LLM，形成可治理的 AI 协作环境。**

资料来源：[CHANGELOG.md:1-30]()

---

<a id='page-2'></a>

## 系统架构与代码组织

### 相关页面

相关主题：[Arkon 总览与产品定位](#page-1), [MRP 管线、Wiki 起草与修订工作流](#page-3), [部署、迁移、运维与已知故障模式](#page-8)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [docker-compose.yml](https://github.com/nduckmink/arkon/blob/main/docker-compose.yml)
- [app/main.py](https://github.com/nduckmink/arkon/blob/main/app/main.py)
- [app/database/models.py](https://github.com/nduckmink/arkon/blob/main/app/database/models.py)
- [app/api/scopes.py](https://github.com/nduckmink/arkon/blob/main/app/api/scopes.py)
- [alembic/env.py](https://github.com/nduckmink/arkon/blob/main/alembic/env.py)
- [alembic/versions/](https://github.com/nduckmink/arkon/tree/main/alembic/versions)
- [.env.docker](https://github.com/nduckmink/arkon/blob/main/.env.docker)
</details>

# 系统架构与代码组织

## 1. 总体定位与技术栈

Arkon 是一个面向团队知识管理的 Web 应用,核心能力是把用户上传的文档抽取、索引后生成结构化 Wiki,并在此之上接入大语言模型(Claude / Gemini)以支持问答与写作场景。整个系统由后端 API、前端 SPA、关系型数据库与外部 LLM 服务组成,通过 Docker Compose 一键编排。

后端采用 Python + FastAPI 的异步栈,通过 SQLAlchemy 异步引擎驱动 PostgreSQL(`postgresql+asyncpg://`),数据库 schema 由 Alembic 管理。前端是独立的 SPA 客户端,通过 HTTP/JSON 调用后端 API。 资料来源：[app/main.py:1-80]() 资料来源：[docker-compose.yml:1-60]() 资料来源：[alembic/env.py:1-30]()

```mermaid
flowchart LR
  Browser[SPA 浏览器] -->|HTTPS/JSON| API[FastAPI 应用]
  API --> ORM[SQLAlchemy Async]
  ORM --> PG[(PostgreSQL)]
  API --> LLM[(Claude / Gemini API)]
  API --> FS[(文件存储)]
  Alembic[Alembic 迁移] --> PG
```

## 2. 仓库目录结构

代码按"职责分层 + 路由聚合"划分,主要模块如下:

- `app/main.py` — 应用入口,负责加载配置、装配中间件、注册路由,是所有 router 唯一的挂载点。 资料来源：[app/main.py:1-80]()
- `app/database/models.py` — ORM 模型集中定义,涵盖用户、Workspace、Source、WikiPage、Action、ScopeMembership、ScopeRole 等实体,作为整个系统的"真相源"。 资料来源：[app/database/models.py:1-200]()
- `app/api/` — 业务路由目录,例如 `scopes.py` 负责访问控制相关端点。 资料来源：[app/api/scopes.py:1-40]()
- `alembic/versions/` — 数据库迁移脚本,目前编号为 `031_xxx` 至 `036_xxx` 序列。 资料来源：[alembic/versions/]()
- `docker-compose.yml` 与 `.env.docker` — 容器编排与环境变量入口。 资料来源：[docker-compose.yml:1-60]()

## 3. 核心模块

### 3.1 数据模型层

`app/database/models.py` 是跨模块唯一允许引用模型的入口。Issue #10 显示 `app/api/scopes.py` 从 `app.database.models` 引入 `Action`、`ScopeMembership`、`ScopeRole`,它们构成了访问控制层的核心实体。该 Issue 同时暴露出一个问题:在 commit `1198a5b` 中,scopes router 引用了 `app.database.models` 中并不存在的名称,导致 API 进程启动时崩溃。 资料来源：[app/api/scopes.py:1-20]() 资料来源：[#10]()

### 3.2 路由注册层

`app/main.py` 通过 `app.include_router(...)` 显式注册各业务模块。Issue #8 的根因是 scopes router 从未被 `import` 与 `include_router`,导致 `/api/scopes/*` 全部返回 404;Issue #10 进一步显示,即便注册了 router,模型导入错误仍会让进程进入 crash loop。 资料来源：[app/main.py:1-80]() 资料来源：[#8]() 资料来源：[#10]()

### 3.3 文档与 Wiki 处理链路

`Source` 模型存储抽取后的全文,Wiki 由多张 `WikiPage` 聚合。Issue #21 揭示:抽取文本中的 NUL 字节(`0x00`)无法被 PostgreSQL `text`/`varchar` 持久化,会在写入 `Source.full_text` 时抛 `CharacterNotInRepertoireError`,应在进入 ORM 之前清洗。Issue #20 则指出 `WikiPage.page_type` 作为 SQLAlchemy `hybrid_property` 必须显式提供 `@page_type.setter`,否则 `POST /api/wiki/pages` 在持久化阶段会触发 500。 资料来源：[#21]() 资料来源：[#20]()

### 3.4 迁移系统

迁移脚本在 `alembic/versions/` 下顺序编号。提交 `5e4069d` 把迁移重排为 `031`–`036` 序列,任何本地数据库在升级时都应先 `alembic upgrade head` 到最新头,再启动应用。 资料来源：[alembic/env.py:1-30]() 资料来源：[#20]()

## 4. 部署与运行

`docker-compose.yml` 统一编排 API、Web、PostgreSQL 等容器。环境变量通过 `.env.docker` 注入,典型配置包含 `POSTGRES_USER`、`POSTGRES_PASSWORD`、`POSTGRES_DB` 与 `DATABASE_URL`。Issue #6 提示:若 `.env` 中 `DATABASE_URL` 密码与 `POSTGRES_PASSWORD` 不一致,后端在启动期会对数据库连接做自检,常见表现是登录阶段提示密码错误。 资料来源：[docker-compose.yml:1-60]() 资料来源：[.env.docker:1-30]() 资料来源：[#6]()

## 5. 演进与社区关注点

v0.9.0 引入了 **Workspace (Projects) Silo Elimination**,删除了旧的"Project"层,把数据与安全边界直接收敛到 Workspace,使模型与路由更扁平。社区近期还集中关注:

- 多语言 Wiki 生成的一致性(#15)
- 自托管 LLM(替代 Claude / Gemini)的可插拔接口(#19)
- 文档抽取与持久化的健壮性(NUL 字节清洗)(#21)

这些诉求集中在 `app/database/models.py` 的 schema 演进以及 `app/main.py` 的 router 注册两点,是后续架构演进的主战场。 资料来源：[v0.9.0 Release Notes]() 资料来源：[#15]() 资料来源：[#19]() 资料来源：[#21]()

---

<a id='page-3'></a>

## MRP 管线、Wiki 起草与修订工作流

### 相关页面

相关主题：[部门作用域、全局作用域与 RBAC 权限引擎](#page-4), [前端 Wiki 浏览、面板与编辑器](#page-7), [部署、迁移、运维与已知故障模式](#page-8)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [app/ai/mrp/pipeline.py](https://github.com/nduckmink/arkon/blob/main/app/ai/mrp/pipeline.py)
- [app/ai/mrp/mapper.py](https://github.com/nduckmink/arkon/blob/main/app/ai/mrp/mapper.py)
- [app/ai/mrp/merger.py](https://github.com/nduckmink/arkon/blob/main/app/ai/mrp/merger.py)
- [app/ai/mrp/writer.py](https://github.com/nduckmink/arkon/blob/main/app/ai/mrp/writer.py)
- [app/ai/mrp/verifier.py](https://github.com/nduckmink/arkon/blob/main/app/ai/mrp/verifier.py)
- [app/services/wiki_service.py](https://github.com/nduckmink/arkon/blob/main/app/services/wiki_service.py)
- [app/api/v1/wiki.py](https://github.com/nduckmink/arkon/blob/main/app/api/v1/wiki.py)
</details>

# MRP 管线、Wiki 起草与修订工作流

## 概述与定位

Arkon 的 MRP（Mapper → Merger → Writer → Verifier）管线是 Wiki 内容生成的核心协调器，负责把已索引的 Source 文档转化为结构化的 Wiki 页面。`app/ai/mrp/pipeline.py` 作为编排入口，按顺序串联各阶段模块并产出最终草稿；`app/services/wiki_service.py` 则负责将这些草稿持久化为 `WikiPage`、`PageRevision` 等数据库实体，并承接后续的人工修订与版本回滚。v0.9.0 取消 Workspace 之外的历史 Project 层后，所有 Wiki 数据都收敛到 Workspace 作用域下，权限校验与可见性均由 `wiki_service.py` 统一处理。

资料来源：[app/ai/mrp/pipeline.py:1-40](), [app/services/wiki_service.py:1-60]()

## MRP 四阶段管线

`app/ai/mrp/pipeline.py` 将整个生成过程拆分为四个顺序执行的阶段，每个阶段对应一个独立模块。下表汇总了各阶段的职责与输入输出契约：

| 阶段 | 模块 | 输入 | 输出 |
|------|------|------|------|
| Mapping | `mapper.py` | `Source.full_text` 集合 | 主题 → 证据片段映射 |
| Merging | `merger.py` | 多 Source 的主题映射 | 按主题归并后的章节大纲 |
| Writing | `writer.py` | 章节大纲 + 证据片段 | Markdown 草稿正文 |
| Verifying | `verifier.py` | 草稿 + 证据片段 | 置信度标记与发布门禁 |

**Mapper 阶段**接收 ETL 已抽取的纯文本，识别主题、实体与候选章节边界。如果提取出的文本包含 NUL 字节（`0x00`），PostgreSQL 的 `text`/`varchar` 列无法存储，将抛出 `CharacterNotInRepertoireError`——这正是 issue #21 所描述的索引失败链路，根因位于 ETL → Mapper 的交接处而非 LLM 调用本身。

**Merger 阶段**的目标是解决 issue #9 中用户反馈的"Wiki 把所有 Sources 堆在一起导致混乱"的现象：对来自不同 Source 的同名主题做归并与去重，输出唯一的主题清单与每个主题下的证据片段集合，确保后续 Writer 不会重复撰写同一概念。

**Writer 阶段**把章节大纲与证据片段送入 LLM 生成最终 Markdown。issue #15 中用户提出"创建 Wiki 时无法选择主语言"的问题，正是因为该阶段目前没有把目标语言作为显式参数注入提示词，导致中文、英文页面混杂。

资料来源：[app/ai/mrp/mapper.py:20-80](), [app/ai/mrp/merger.py:15-70](), [app/ai/mrp/writer.py:25-120](), [app/ai/mrp/verifier.py:10-55]()

## Wiki 起草与修订生命周期

`app/services/wiki_service.py` 暴露了 Wiki 完整生命周期管理接口，配合 `app/api/v1/wiki.py` 的 REST 端点对外提供服务：

- `create_draft(workspace_id, source_ids)`：触发完整 MRP 管线，产出初始 `WikiPage` 与 `PageRevision(status="draft")`。
- `publish_page(page_id)`：将 `draft` 提升为 `published`，写入发布时间戳与作者。
- `revise_page(page_id, new_source_ids)`：当 Workspace 内新增 Source 时，对受影响的页面执行增量 MRP，写入新 `PageRevision`，旧版本保留用于回滚。
- `manual_edit(page_id, content)`：用户在 UI 中直接编辑正文，生成由人创作的 `PageRevision` 并保留作者信息。

issue #20 报告的"创建 Wiki 页面返回 500"问题，正是 `WikiPage.page_type` 使用了 SQLAlchemy `hybrid_property`（用于在 ORM 与 Pydantic schema 间转换）但缺少显式 setter，导致 Pydantic 反序列化时无法写入该字段。`wiki_service.create_draft` 抛出的 500 在前端表现为 "Failed to fetch"。修复需要在 Pydantic schema 中显式声明 `page_type` 字段或为 `hybrid_property` 提供 setter。

资料来源：[app/services/wiki_service.py:80-200](), [app/api/v1/wiki.py:30-110]()

## 已知限制与社区反馈

与 MRP / Wiki 工作流直接相关的社区反馈可归纳如下：

- **issue #9** — Wiki 把所有 Sources 无序叠加：根因在 Merger 阶段主题归并未生效或被绕过，需要重新启用归并逻辑并把归并结果回写到 Page 的章节索引。
- **issue #15** — 无法选择 Wiki 主语言：根因在 Writer 阶段未把语言作为参数传入 LLM 提示词，需在 `create_draft` 入口增加 `language` 字段并透传至 `writer.py`。
- **issue #20** — 创建 Wiki 页面 500：根因在 `hybrid_property` 缺 setter，需在 Pydantic schema 中补齐 `page_type` 字段。
- **issue #21** — 文档索引失败（NUL 字节）：根因在 ETL → Mapper 链路未做字符过滤，需在 Mapper 入口处对 `0x00` 做剥离或替换。

资料来源：[app/ai/mrp/pipeline.py:55-95](), [app/services/wiki_service.py:140-180]()

## 配置与扩展点

要在企业内网用自托管模型替换 Claude / Gemini（issue #19 的诉求），需要让 `app/ai/mrp/writer.py` 与 `verifier.py` 中的 LLM 客户端变为可注入依赖。当前两个文件直接引用 Claude / Gemini SDK，切换本地模型（如 Gemma4、Qwen）时需要改写调用层并补齐提示词模板。`app/ai/mrp/pipeline.py` 的阶段开关（是否跳过 Verifier、Mapper 的批大小等）通过环境变量控制，便于在不同部署环境下裁剪管线。`wiki_service.py` 中的修订策略（如修订时是否合并人工编辑与自动生成）也可通过配置项调整，以适应不同团队的协作习惯。

资料来源：[app/ai/mrp/writer.py:30-90](), [app/ai/mrp/verifier.py:20-60](), [app/ai/mrp/pipeline.py:45-80]()

---

<a id='page-4'></a>

## 部门作用域、全局作用域与 RBAC 权限引擎

### 相关页面

相关主题：[MCP 服务器、OAuth 2.1 与 Claude 接入](#page-5), [前端 Wiki 浏览、面板与编辑器](#page-7)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [app/routers/scopes.py](https://github.com/nduckmink/arkon/blob/main/app/routers/scopes.py)
- [app/routers/rbac.py](https://github.com/nduckmink/arkon/blob/main/app/routers/rbac.py)
- [app/services/permission_engine.py](https://github.com/nduckmink/arkon/blob/main/app/services/permission_engine.py)
- [app/services/policy_engine.py](https://github.com/nduckmink/arkon/blob/main/app/services/policy_engine.py)
- [app/services/permissions.py](https://github.com/nduckmink/arkon/blob/main/app/services/permissions.py)
- [app/services/audit_service.py](https://github.com/nduckmink/arkon/blob/main/app/services/audit_service.py)
</details>

# 部门作用域、全局作用域与 RBAC 权限引擎

## 概述与设计目标

Arkon 的访问控制以"作用域（Scope）+ 角色（Role）+ 操作（Action）"三要素为核心，将用户划分为全局管理员、跨部门成员、单部门成员等层级，从而在同一租户内实现"按部门隔离、按角色授权、按操作校验"的细粒度权限管理。`app/services/permission_engine.py` 暴露高层 `PermissionEngine` 门面，`app/services/policy_engine.py` 提供策略表达与求值能力，`app/services/permissions.py` 则集中定义权限矩阵与工具函数，三者共同构成 RBAC 权限引擎。资料来源：[app/services/permission_engine.py]() [app/services/policy_engine.py]() [app/services/permissions.py]()

设计目标包含：

- **多租户隔离**：通过 `Scope` 的 `scope_type`（`global`/`department`/`workspace`）区分全局作用域与部门作用域，避免跨部门数据泄露。资料来源：[app/routers/scopes.py]()
- **角色可继承**：部门管理员可继承全局只读角色，跨部门成员可聚合多个 `ScopeMembership` 的有效角色集。资料来源：[app/services/permission_engine.py]()
- **可审计**：所有授权决策经 `audit_service.py` 落库，满足合规追溯要求。资料来源：[app/services/audit_service.py]()

## 数据模型与作用域层级

权限引擎依赖四张核心表——`Action`、`Scope`、`ScopeRole`、`ScopeMembership`，它们都通过 `app/database/models.py` 暴露给 ORM：

| 模型 | 关键字段 | 作用 |
|------|---------|------|
| `Action` | `name`, `resource`, `effect` | 定义可被授权的最小动作单元（如 `wiki:read`） |
| `Scope` | `id`, `scope_type`, `parent_id` | 描述部门/全局/工作空间三类作用域并支持父子继承 |
| `ScopeRole` | `scope_id`, `role_name`, `permissions` | 在作用域内绑定角色与权限集合 |
| `ScopeMembership` | `user_id`, `scope_id`, `role_id` | 用户在某个作用域中的成员身份与角色 |

资料来源：[app/routers/scopes.py]() [app/services/permission_engine.py]()。社区 issue #10 报告过 `scopes.py` 早期从 `app.database.models` 导入 `Action`/`ScopeMembership`/`ScopeRole` 时出现未定义名称，正是因为上述模型曾在重构中遗漏注册。资料来源：[GitHub Issue #10]()

## RBAC 权限引擎工作流

权限校验按"加载身份 → 收集作用域 → 求值策略 → 写审计"四步进行：

1. **加载身份**：从 JWT 取出 `user_id`，调用 `PermissionEngine.resolve_principal()` 汇总该用户在 `global`、`department`、`workspace` 三类作用域中的全部 `ScopeMembership`。资料来源：[app/services/permission_engine.py]()
2. **收集作用域**：`policy_engine.evaluate()` 根据目标资源的 `scope_id` 沿 `parent_id` 向上回溯，收集所有可继承的作用域；全局作用域作为根节点始终参与。资料来源：[app/services/policy_engine.py]()
3. **求值策略**：在收集到的 `(scope, role, action)` 三元组上调用 `permissions.check()`，合并 `allow`/`deny` 规则并处理冲突——显式拒绝优先于显式允许。资料来源：[app/services/permissions.py]()
4. **写审计**：决策结果与上下文（IP、客户端、租户）一并由 `AuditService.record()` 持久化，便于事后追溯。资料来源：[app/services/audit_service.py]()

```mermaid
flowchart TD
  A[请求进入] --> B[resolve_principal]
  B --> C[加载 ScopeMembership]
  C --> D[沿 parent_id 收集作用域]
  D --> E[policy_engine.evaluate]
  E --> F[permissions.check 合并 allow/deny]
  F --> G[AuditService.record]
  G --> H{是否允许}
  H -->|是| I[执行业务]
  H -->|否| J[返回 403]
```

## API 路由与社区反馈

`app/routers/scopes.py` 提供 `/api/scopes`、`/api/scopes/{id}/members` 等端点，`app/routers/rbac.py` 则提供 `/api/rbac/check`、`/api/rbac/roles` 等查询接口。两组路由都依赖 `PermissionEngine` 作为依赖注入项。资料来源：[app/routers/scopes.py]() [app/routers/rbac.py]()

社区中常见的几类问题与本引擎直接相关：

- **路由缺失（issue #8/#10）**：scopes 路由未被 `app/main.py` 注册会导致 404，需确认 `app.include_router(scopes.router, prefix="/api/scopes")` 存在；此外 `Action`、`ScopeMembership`、`ScopeRole` 必须从 `app.database.models` 正确导出。资料来源：[GitHub Issue #8]() [GitHub Issue #10]()
- **跨部门授权（issue #11）**：用户希望单一账号同时加入 Product 与 Sales 部门，引擎通过 `ScopeMembership` 的多条记录天然支持，关键在于 RBAC 前端在切换部门时使用对应 `scope_id` 重新发起权限检查。资料来源：[GitHub Issue #11]()
- **Workspaces 添加成员（issue #4）**：早期前端硬编码 `role="member"`，与 RBAC 定义的 `viewer`/`editor`/`admin` 不匹配，权限引擎会拒绝非法角色——修复方式是在前端引入 `selectedRole` 状态后调用 `/api/rbac/roles` 列出合法值。资料来源：[GitHub Issue #4]()

## 最佳实践与扩展建议

- **优先使用全局作用域作为兜底**：将"读取公共知识库"等操作挂在 `scope_type='global'`，确保任何有效成员都至少具备只读权限。资料来源：[app/services/permission_engine.py]()
- **deny 规则集中维护**：在 `permissions.py` 中以显式 `deny` 表达例外（如禁止部门成员查看薪资文档），避免在策略引擎分支中散落特例。资料来源：[app/services/permissions.py]()
- **审计与策略同步演进**：新增 `Action` 后务必同步在 `audit_service.py` 中登记敏感操作，避免审计盲区。资料来源：[app/services/audit_service]()
- **避免角色名漂移**：前端选择角色时应通过 `/api/rbac/roles` 拉取动态列表，而非硬编码字符串，防止再次出现 issue #4 那类"role 不在 RBAC 字典"的故障。资料来源：[app/routers/rbac.py]()

> 注：行内链接采用 `[path/to/file.ext:line-line]()` 占位形式，正式发布时需替换为带 commit hash 的具体行号链接，以满足"逐行可追溯"要求。

---

<a id='page-5'></a>

## MCP 服务器、OAuth 2.1 与 Claude 接入

### 相关页面

相关主题：[部门作用域、全局作用域与 RBAC 权限引擎](#page-4), [AI Provider 目录、模型选型与嵌入迁移](#page-6)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [app/mcp/server.py](https://github.com/nduckmink/arkon/blob/main/app/mcp/server.py)
- [app/mcp/tools.py](https://github.com/nduckmink/arkon/blob/main/app/mcp/tools.py)
- [app/mcp/resources.py](https://github.com/nduckmink/arkon/blob/main/app/mcp/resources.py)
- [app/mcp/permissions.py](https://github.com/nduckmink/arkon/blob/main/app/mcp/permissions.py)
- [app/mcp/middleware.py](https://github.com/nduckmink/arkon/blob/main/app/mcp/middleware.py)
- [app/services/mcp_auth_service.py](https://github.com/nduckmink/arkon/blob/main/app/services/mcp_auth_service.py)
- [app/main.py](https://github.com/nduckmink/arkon/blob/main/app/main.py)
</details>

# MCP 服务器、OAuth 2.1 与 Claude 接入

Arkon 通过内置的 **Model Context Protocol (MCP)** 服务器，将平台内的知识库（Wiki）、文档（Documents）、工作区（Workspaces）以及权限（Scopes）以结构化的 *tools* 和 *resources* 形式暴露给外部 LLM 客户端。当用户将 Claude（或兼容 MCP 的客户端）连接到 Arkon 时，所有调用必须经过 **OAuth 2.1** 授权流程，从而保证每一次工具调用都与特定用户身份及权限范围绑定，避免“密钥共享”问题（参见社区 issue #5 关于 skill 防扩散的讨论）。

## 整体架构

MCP 服务器在 FastAPI 应用启动时被注册到主路由中，与 REST API 共用同一套数据库会话与权限模型。`app/main.py` 在应用工厂中挂载 MCP 路由，使得 `/mcp` 端点对外提供 JSON-RPC over HTTP/SSE 协议；下游客户端（如 Claude Desktop）发起 `initialize`、`tools/list`、`tools/call` 等标准 MCP 请求。

资料来源：[app/mcp/server.py:1-120]()、[app/main.py:80-140]()

```mermaid
flowchart LR
    A[Claude Desktop] -->|OAuth 2.1 Token| B(MCP Server)
    B --> C[Middleware<br/>鉴权与审计]
    C --> D[Tools 层]
    C --> E[Resources 层]
    D --> F[(PostgreSQL<br/>Workspaces / Documents)]
    E --> F
    B -.审计日志.-> G[(audit_log)]
```

## OAuth 2.1 授权流程

授权由 `app/services/mcp_auth_service.py` 实现，遵循 OAuth 2.1 规范（Authorization Code + PKCE），这是 MCP 官方推荐的授权模式：

- **客户端注册**：用户在 Arkon 控制台为 Claude Desktop 创建 MCP 客户端，得到 `client_id` 与 `client_secret`。
- **授权请求**：`/oauth/authorize` 端点展示同意页面，确认授权范围（read:wiki、write:document 等）。
- **Token 颁发**：`/oauth/token` 在校验 `code_verifier` 后签发短期 `access_token`（默认 1 小时）与可刷新的 `refresh_token`。
- **作用域校验**：每次调用 MCP 工具时，`mcp_auth_service` 会解析 JWT 声明中的 `scope`，并与数据库中的 `ScopeMembership`、`ScopeRole`、`Action` 进行比对（参见 issue #10 中关于 `Action`、`ScopeMembership`、`ScopeRole` 导入问题的修复）。

资料来源：[app/services/mcp_auth_service.py:1-200]()、[app/mcp/middleware.py:30-90]()

社区反馈显示，issue #7 中“无法连接 Claude”的常见原因之一即 OAuth Token 未携带正确的 `aud` 声明或 `scope` 与 MCP 工具要求不匹配，建议在客户端配置中显式声明 `resource` 参数。

## 工具（Tools）与资源（Resources）

MCP 协议将能力划分为两类：

- **Tools**：可调用的函数，例如 `search_documents`、`create_wiki_page`、`upload_document`。工具实现在 `app/mcp/tools.py` 中，对应平台内部的 Service 层（如 `document_service`、`wiki_service`），并通过 `permissions.py` 的装饰器 `require_scope` 进行权限校验。
- **Resources**：只读的资源 URI，例如 `wiki://page/{id}`、`document://{id}`。`app/mcp/resources.py` 负责将资源 URI 映射到数据库查询，并按当前用户的可见性过滤结果。

下表列出 Arkon 暴露的核心 MCP 工具与所需作用域：

| 工具名称 | 用途 | 所需最小作用域 |
|----------|------|----------------|
| `search_documents` | 在工作区内全文检索文档 | `documents:read` |
| `create_wiki_page` | 创建 Wiki 页面（issue #20 修复 `page_type` setter 后可用） | `wiki:write` |
| `upload_document` | 上传并索引新文档 | `documents:write` |
| `list_workspaces` | 列出当前用户所属工作区 | `workspaces:read` |

资料来源：[app/mcp/tools.py:40-220]()、[app/mcp/resources.py:25-160]()、[app/mcp/permissions.py:1-180]()

## 中间件、安全与已知问题

`app/mcp/middleware.py` 串联了以下横切关注点：

1. **身份验证**：从 `Authorization: Bearer <token>` 解析 JWT，并将 `current_user` 注入请求上下文。
2. **作用域映射**：将 token 中的 `scope` 字符串转换为平台内部的 `ScopeRole`，再由 `permissions.py` 的 `Action` 枚举决定是否放行。
3. **审计日志**：所有 `tools/call` 请求写入 `audit_log`，包括调用者、IP、工具名、参数摘要，便于事后追溯。
4. **速率限制**：基于用户与工作区维度的令牌桶，防止 Claude 在长会话中触发过多索引请求。

资料来源：[app/mcp/middleware.py:1-150]()、[app/mcp/permissions.py:60-140]()

需要注意的是，issue #19 中用户希望使用自托管 LLM（如 Qwen、Gemma）替代 Claude；目前 Arkon 的 MCP 接入层与具体 LLM 解耦，只要客户端能完成 OAuth 2.1 握手并遵循 MCP 协议，理论上任何兼容客户端均可接入。对于仍在使用早期 MCP 版本或未实现 PKCE 的客户端，建议升级至最新 Claude Desktop 以确保与 `mcp_auth_service` 的握手成功。

---

<a id='page-6'></a>

## AI Provider 目录、模型选型与嵌入迁移

### 相关页面

相关主题：[MRP 管线、Wiki 起草与修订工作流](#page-3), [MCP 服务器、OAuth 2.1 与 Claude 接入](#page-5)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [app/ai/registry.py](https://github.com/nduckmink/arkon/blob/main/app/ai/registry.py)
- [app/ai/llm_catalog.py](https://github.com/nduckmink/arkon/blob/main/app/ai/llm_catalog.py)
- [app/ai/embedding_catalog.py](https://github.com/nduckmink/arkon/blob/main/app/ai/embedding_catalog.py)
- [app/ai/vision_catalog.py](https://github.com/nduckmink/arkon/blob/main/app/ai/vision_catalog.py)
- [app/ai/providers/anthropic_provider.py](https://github.com/nduckmink/arkon/blob/main/app/ai/providers/anthropic_provider.py)
- [app/ai/providers/google.py](https://github.com/nduckmink/arkon/blob/main/app/ai/providers/google.py)
</details>

# AI Provider 目录、模型选型与嵌入迁移

## 概述

Arkon 通过 `app/ai/` 包统一管理大语言模型、嵌入模型与视觉模型的接入。该模块以「目录（catalog）+ 注册表（registry）+ 提供商适配（provider）」三层结构组织后端 AI 能力：目录层声明可用模型元数据，注册表层根据用户配置或环境变量动态解析具体模型，提供商层负责调用外部 API（如 Anthropic Claude、Google Gemini）并完成协议封装。`registry.py` 是整个子系统的入口，业务模块（包括 wiki 生成、文档嵌入、检索问答）只与注册表交互，从而屏蔽底层厂商差异。

资料来源：[app/ai/registry.py:1-40](), [app/ai/llm_catalog.py:1-25]()

## 目录层：LLM、Embedding、Vision 三类模型清单

`llm_catalog.py`、`embedding_catalog.py`、`vision_catalog.py` 三个文件分别维护三张模型清单：

| 文件 | 内容 | 典型条目 |
| --- | --- | --- |
| `llm_catalog.py` | 文本生成模型 | `claude-3-5-sonnet`、`claude-3-opus`、`gemini-1.5-pro` 等 |
| `embedding_catalog.py` | 文本向量化模型 | 各厂商的嵌入模型及对应的维度（dimension） |
| `vision_catalog.py` | 多模态视觉模型 | 支持图像输入的 Claude、Gemini 系列 |

每个目录条目以只读数据结构（dataclass 或 `TypedDict`）记录 `model_id`、`provider`、`max_tokens`、`supports_streaming`、`input_modalities` 等字段，供注册表在运行时做能力匹配。业务侧通过目录枚举模型，而非硬编码字符串，以便于新增厂商时仅修改目录文件。

资料来源：[app/ai/llm_catalog.py:1-60](), [app/ai/embedding_catalog.py:1-45](), [app/ai/vision_catalog.py:1-30]()

## 注册表层：模型选型与运行时解析

`registry.py` 是模型选型的核心。模块启动时收集环境变量（如 `ARKON_LLM_MODEL`、`ARKON_EMBEDDING_MODEL`）以及用户在 workspace / 系统设置中持久化的偏好，然后在目录中查找匹配项，若未命中则回退到默认模型。函数签名通常形如 `resolve_llm(scope: str) -> LLMHandle` 与 `resolve_embedding(scope: str) -> EmbeddingHandle`，scope 用于区分 workspace 级别与系统级别的选型。

```mermaid
flowchart LR
  A[业务调用] --> B[registry.resolve_*]
  B --> C{目录中存在?}
  C -- 是 --> D[加载 Provider]
  C -- 否 --> E[回退默认模型]
  D --> F[anthropic_provider / google]
  F --> G[外部 LLM API]
```

注册表同时负责「能力探测」：例如在生成 wiki 时检测所选 LLM 是否支持 `vision`、是否支持 `tool_use`，若不支持则降级到仅文本路径。社区中 issue #7「无法连接 Claude」即触发于该层——当 API Key 未配置或网络不可达时，注册表在初始化阶段抛出 `ProviderUnavailable`，上层返回 502。

资料来源：[app/ai/registry.py:41-110](), [app/ai/providers/anthropic_provider.py:1-55]()

## 提供商适配层：Anthropic 与 Google

`app/ai/providers/` 子包按厂商隔离实现：

- `anthropic_provider.py` 封装 Anthropic Messages API，处理 `system`、`user`、`assistant` 消息结构、tool use、流式响应。模型标识直接透传 `claude-*` 字串。
- `google.py` 封装 Google Generative AI SDK（Gemini），负责将 OpenAI 风格消息转换为 Gemini 的 `contents` / `parts` 结构。

两个适配器对外暴露统一的 `invoke()`、`stream()`、`embed()` 接口（按目录类型裁剪），注册表只通过接口与厂商解耦。issue #19 中用户希望替换为自托管 Gemma4/Qwen，只需新增一个 `self_hosted.py` 适配器并在目录中加入条目，无需改动注册表与业务调用方。

资料来源：[app/ai/providers/anthropic_provider.py:56-130](), [app/ai/providers/google.py:1-90]()

## 嵌入模型与迁移

`embedding_catalog.py` 与嵌入迁移紧密相关：

1. **维度记录**：每条目记录 `dimension`，迁移前先比较源库与目标模型维度是否一致；不一致时需要重新生成全部向量。
2. **批量嵌入任务**：注册表暴露 `embed_documents(texts: list[str])`，用于在文档索引（见 issue #21 提到的 NUL 字节清洗）中批量生成向量。文本进入嵌入前需先去除 `\x00`，否则 PostgreSQL 拒存。
3. **迁移流程**：管理员在 UI 中切换 embedding provider 后，系统调度后台任务：清空旧向量列 → 重新调用 `embed_documents()` → 写回新向量。注册表会在任务结束前锁定该 scope，避免中途并发检索拿到不一致向量。

资料来源：[app/ai/embedding_catalog.py:46-110](), [app/ai/registry.py:111-160]()

## 实践注意事项

- 切换 LLM 或 embedding provider 后，必须清理已生成的缓存（wiki 摘要、检索索引），否则会得到混合向量导致检索质量下降。
- Anthropic 与 Google 的速率限制、超时、重试策略分别在各自 provider 文件中实现，业务层不感知。
- 当用户计划引入自托管模型（issue #19），应优先实现与现有目录条目同构的 provider，并在 `llm_catalog.py` 中声明能力（`supports_streaming`、`input_modalities`），否则注册表的能力探测会将其错配到不支持的调用路径。

---

<a id='page-7'></a>

## 前端 Wiki 浏览、面板与编辑器

### 相关页面

相关主题：[MRP 管线、Wiki 起草与修订工作流](#page-3), [部门作用域、全局作用域与 RBAC 权限引擎](#page-4)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [frontend/src/app/(portal)/layout.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/app/(portal)/layout.tsx)
- [frontend/src/app/(portal)/wiki/[...slug]/page.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/app/(portal)/wiki/[...slug]/page.tsx)
- [frontend/src/app/(portal)/wiki/page.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/app/(portal)/wiki/page.tsx)
- [frontend/src/components/wiki/wiki-editor.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/components/wiki/wiki-editor.tsx)
- [frontend/src/components/wiki/wiki-create-page-dialog.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/components/wiki/wiki-create-page-dialog.tsx)
- [frontend/src/components/wiki/wiki-content.tsx](https://github.com/nduckmink/arkon/blob/main/frontend/src/components/wiki/wiki-content.tsx)
</details>

# 前端 Wiki 浏览、面板与编辑器

Arkon 的 Wiki 功能在 v0.9.0 中完成了一次重大架构简化（移除 Project 层后，Workspace 直接承载知识库），前端对应的浏览、面板与编辑器组件也按职责被切分为独立的客户端组件。本页基于 `frontend/src/app/(portal)/wiki/**` 与 `frontend/src/components/wiki/**` 的源码，梳理 Wiki 前端的路由布局、列表页与详情页、创建对话框以及编辑器组件之间的协作关系，并结合社区反馈指出当前的边界与限制。

## 1. 路由与布局：`(portal)/wiki`

Wiki 模块位于受保护路由组 `(portal)` 下，所有页面都共享 `(portal)/layout.tsx` 提供的全局外壳（包括侧边栏、顶部栏以及鉴权上下文）。在该布局之下，`wiki/` 目录负责把 Wiki 相关 UI 暴露为 Next.js App Router 的路由：

- `frontend/src/app/(portal)/wiki/page.tsx` 是 Wiki 入口页面，对应 `/wiki`，主要承载 Wiki 列表、搜索与“新建页面”入口。
- `frontend/src/app/(portal)/wiki/[...slug]/page.tsx` 是通配子路由，对应 `/wiki/<slug>` 形式的任意层级页面（例如 `/wiki/team/onboarding`），用于渲染具体的 Wiki 页面内容。

这种 `page.tsx` + `[...slug]/page.tsx` 的组合让 Wiki 在前端呈现为树状可嵌套的浏览结构，便于把组织内的知识按 Workspace → 分类 → 页面 分层。资料来源：[frontend/src/app/(portal)/layout.tsx:1-40]()、[frontend/src/app/(portal)/wiki/page.tsx:1-60]()、[frontend/src/app/(portal)/wiki/[...slug]/page.tsx:1-60]()。

## 2. Wiki 浏览面板：`wiki-content.tsx`

`wiki-content.tsx` 是详情页的核心展示组件，负责把后端返回的 Wiki 页面内容渲染为可阅读的视图。它承担以下职责：

- 接收路由参数（来自 `[...slug]/page.tsx`）并解析出当前页面 slug；
- 调用 Wiki API 拉取页面元数据（标题、层级、来源引用等）以及正文内容；
- 把内容以结构化的方式呈现，例如分章节、列出引用来源（references/citations）以及关联的 Sources。

社区 Issue #9 反馈 Wiki 面板“把所有 sources 混在一起，看不出属于哪一篇文档”，因此渲染时需要明确每条 Source 所属的原始文档（doc id / 来源名）。`wiki-content.tsx` 在做引用渲染时应保留来源归属，避免面板再次出现“来源堆叠”的体验问题。资料来源：[frontend/src/components/wiki/wiki-content.tsx:1-120]()。

## 3. 新建页面：`wiki-create-page-dialog.tsx`

`wiki-create-page-dialog.tsx` 提供了一个模态对话框，承载“创建 Wiki 页面”这一高频操作。其典型流程包括：

1. 用户在 `/wiki` 或任意 Wiki 页面顶部点击“新建页面”按钮；
2. 对话框弹出，收集标题、父页面（slug 路径）、正文初始内容；
3. 提交后调用 `POST /api/wiki/pages` 创建页面，并跳转到对应的 `/wiki/<new-slug>`。

需要重点关注的是 Issue #20 描述的故障：调用 `POST /api/wiki/pages` 时返回 500，根因在后端 `page_type` 是 SQLAlchemy 的 `hybrid_property`，仅暴露读取器而缺少 `@hybrid_property.setter`，因此 ORM 在写入时无法回填字段。当前组件在错误处理上应捕获 500 并把后端错误信息（如 “Failed to fetch”）透出给用户，避免前端仅展示通用网络错误。资料来源：[frontend/src/components/wiki/wiki-create-page-dialog.tsx:1-160]()。

另外，Issue #15 提议在创建 Wiki 时允许指定主语言（例如限定整站统一为越南语或英文），从而避免“英文 page + 越南文 page”混杂。当前 `wiki-create-page-dialog.tsx` 尚未提供语言字段，是后续需要扩展的点。资料来源：[frontend/src/components/wiki/wiki-create-page-dialog.tsx:1-160]()。

## 4. Wiki 编辑器：`wiki-editor.tsx`

`wiki-editor.tsx` 是 Wiki 的内容编辑组件，用于在已有页面上进行修改并保存。它与浏览面板共用同一布局：

- 在浏览模式下，`wiki-content.tsx` 渲染只读视图；
- 点击“编辑”后，`wiki-editor.tsx` 接管当前页面的正文区域，进入可编辑状态；
- 保存时调用 `PUT /api/wiki/pages/{slug}` 把更新后的内容写回后端。

编辑组件需要承担与创建对话框类似但更细的职责，包括：自动保存草稿、版本/冲突提示、以及与 `Sources` 关联关系的维护。Issue #12 中反映的“编辑文档报错”通常发生在保存阶段，前端应在 `wiki-editor.tsx` 的提交逻辑中显式区分网络错误、权限错误与后端 4xx/5xx，并在 UI 上给出明确提示。资料来源：[frontend/src/components/wiki/wiki-editor.tsx:1-200]()。

## 5. 数据流概览

下面用一张表格总结 Wiki 前端各组件的职责、对应路由以及所依赖的 API：

| 组件 / 路由文件 | 路由 | 主要职责 | 主要 API |
| --- | --- | --- | --- |
| `(portal)/layout.tsx` | 全部 `(portal)` 子路由 | 全局外壳、鉴权、侧边栏 | — |
| `wiki/page.tsx` | `/wiki` | Wiki 列表与入口 | `GET /api/wiki/pages` |
| `wiki/[...slug]/page.tsx` | `/wiki/<slug>` | 详情页容器 | `GET /api/wiki/pages/{slug}` |
| `wiki-content.tsx` | 详情页内 | 渲染只读内容与引用 | 复用页面 API 返回值 |
| `wiki-create-page-dialog.tsx` | 任意页面触发 | 创建新页面 | `POST /api/wiki/pages` |
| `wiki-editor.tsx` | 详情页编辑态 | 编辑/保存页面 | `PUT /api/wiki/pages/{slug}` |

资料来源：[frontend/src/app/(portal)/wiki/page.tsx:1-60]()、[frontend/src/app/(portal)/wiki/[...slug]/page.tsx:1-60]()、[frontend/src/components/wiki/wiki-content.tsx:1-120]()、[frontend/src/components/wiki/wiki-create-page-dialog.tsx:1-160]()、[frontend/src/components/wiki/wiki-editor.tsx:1-200]()。

## 6. 已知限制与社区关注点

结合社区 Issue，前端 Wiki 模块当前在三方面仍有改进空间：

1. **创建失败提示**：`POST /api/wiki/pages` 在后端 `page_type` 缺失 setter 时返回 500，前端 `wiki-create-page-dialog.tsx` 需要更清晰的错误反馈（Issue #20）。
2. **语言一致性**：创建页面时缺少语言选项，导致同一 Wiki 下出现多语言混杂（Issue #15）。
3. **来源归属**：`wiki-content.tsx` 渲染引用时应明确每条 Source 所属文档，避免“来源堆叠”影响阅读（Issue #9）。

后续开发在扩展 Wiki 前端时，建议先在 `wiki-create-page-dialog.tsx` 与 `wiki-editor.tsx` 中补齐错误边界，再在 `wiki-content.tsx` 中强化来源归属展示，最后在创建对话框中引入语言选择字段，从而覆盖当前社区最关心的三类问题。

---

<a id='page-8'></a>

## 部署、迁移、运维与已知故障模式

### 相关页面

相关主题：[系统架构与代码组织](#page-2), [部门作用域、全局作用域与 RBAC 权限引擎](#page-4), [MRP 管线、Wiki 起草与修订工作流](#page-3)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [Dockerfile](https://github.com/nduckmink/arkon/blob/main/Dockerfile)
- [docker-compose.yml](https://github.com/nduckmink/arkon/blob/main/docker-compose.yml)
- [.env.docker.example](https://github.com/nduckmink/arkon/blob/main/.env.docker.example)
- [entrypoint.sh](https://github.com/nduckmink/arkon/blob/main/entrypoint.sh)
- [app/scripts/seed_skills.py](https://github.com/nduckmink/arkon/blob/main/app/scripts/seed_skills.py)
- [alembic.ini](https://github.com/nduckmink/arkon/blob/main/alembic.ini)
- [app/main.py](https://github.com/nduckmink/arkon/blob/main/app/main.py)
- [app/database/models.py](https://github.com/nduckmink/arkon/blob/main/app/database/models.py)
</details>

# 部署、迁移、运维与已知故障模式

本文档面向运维与平台工程人员，介绍 Arkon 的部署形态、数据库迁移流程、日常运行注意事项，以及社区反馈中常见的故障模式与对应修复要点。

## 一、容器化部署架构

Arkon 通过 Docker Compose 编排一组服务，提供 API、数据库与对象存储的一体化部署。

### 1.1 服务组件

`docker-compose.yml` 中定义的核心服务包括：

- **api**：基于 `Dockerfile` 构建的 FastAPI 后端，是业务入口。
- **db**：PostgreSQL，用于持久化 `Source`、`User`、`Workspace`、`WikiPage` 等业务实体。
- 可选的对象存储服务用于保存上传的原始文档二进制内容。

API 容器启动时，`entrypoint.sh` 会按顺序执行：等待数据库可达 → 运行 Alembic 迁移 → 注入基础技能（`seed_skills.py`）→ 启动 Uvicorn。这一顺序保证了首次启动即可对外提供完整业务能力。资料来源：[docker-compose.yml:1-80]()、[Dockerfile:1-60]()、[entrypoint.sh:1-50]()

### 1.2 启动命令

推荐通过环境文件驱动部署：

```bash
docker compose --env-file .env.docker up -d --build
```

环境变量模板由 `.env.docker.example` 提供，主要包括：`POSTGRES_USER`、`POSTGRES_PASSWORD`、`POSTGRES_DB`、`DATABASE_URL`、`SECRET_KEY`、`ANTHROPIC_API_KEY` 等。资料来源：[.env.docker.example:1-40]()

## 二、数据库迁移（Alembic）

迁移目录由 `alembic.ini` 指向，版本号在 v0.9.0 后被重排为 `031–036` 区间。

### 2.1 迁移执行时机

迁移在容器启动入口脚本中自动运行，无需手动介入：

```text
1. wait-for-db
2. alembic upgrade head
3. python -m app.scripts.seed_skills
4. uvicorn app.main:app
```

`seed_skills.py` 负责插入系统内置的 Claude/Gemini 调用技能，作为初始能力种子。资料来源：[entrypoint.sh:10-40]()、[app/scripts/seed_skills.py:1-50]()

### 2.2 跨版本升级

从旧版本升级时，必须先核对 `alembic/versions/` 下的迁移文件序号是否连续。在 commit `5e4069d` 中执行的迁移重排即是为了消除版本号冲突。若在新克隆的仓库上启动出现 500 错误，应优先怀疑迁移未完成或版本号断层。资料来源：[alembic.ini:1-30]()

## 三、运行时配置与登录初始化

### 3.1 关键环境变量

| 变量 | 作用 | 缺失后果 |
|------|------|----------|
| `SECRET_KEY` | JWT 签名密钥 | 登录失败或 token 校验异常 |
| `DATABASE_URL` | asyncpg 连接串 | API 启动崩溃 |
| `POSTGRES_*` | 容器内建库初始化 | 数据库为空 |
| `ANTHROPIC_API_KEY` | Claude 调用凭据 | Wiki 生成失败 |

### 3.2 登录失败常见原因

社区反馈 issue #6 中，用户使用 `.env` 模板部署后登录持续提示密码错误。经排查，问题往往源于 `DATABASE_URL` 中的用户名/密码与 `POSTGRES_USER`/`POSTGRES_PASSWORD` 不一致，导致 seed 阶段写入的初始管理员哈希与登录时的查询不在同一库中。解决方法是保证连接串与库初始化参数完全一致后再重启 `api` 服务。资料来源：[.env.docker.example:1-40]()

## 四、已知故障模式与修复

### 4.1 API 启动崩溃：scopes router 未注册

issue #10 报告在 commit `1198a5b` 之后，新克隆仓库 `docker compose up` 后 API 反复崩溃。根因是 `app/scopes/router.py` 中从 `app.database.models` 导入了未导出的符号（`Action`、`ScopeMembership`、`ScopeRole`），触发 `ImportError`。修复需在 `app.database.models` 中显式导出这些模型，或在 router 中改为按需 import。issue #8（Access Control Departments 404）也是同一根因的另一种表象：scopes router 从未在 `app/main.py` 中通过 `app.include_router(scopes.router, ...)` 注册。资料来源：[app/main.py:1-80]()、[app/database/models.py:1-200]()

### 4.2 文档索引失败：NUL 字节冲突

issue #21 指出，当抽取出的文档文本包含 `0x00` 时，PostgreSQL 的 `text`/`varchar` 列无法存储，触发 `CharacterNotInRepertoireError`，整个 ingestion 流程被中断。建议在入库前对 `full_text` 做 `text.replace("\x00", "")` 清洗，或在模型层定义 `TypeDecorator` 自动过滤。资料来源：[app/database/models.py:1-200]()

### 4.3 创建 Wiki 页面 500

issue #20 定位到 `WikiPage.page_type` 是一个 `hybrid_property`，但只读无 `@setter`，因此 ORM 在创建时无法回写字段。修复方案是补充对应的 setter，或将 `page_type` 改为普通 `Column`。资料来源：[app/database/models.py:1-200]()

### 4.4 添加成员角色非法

issue #4 中 `handleAddMember` 前端硬编码 `role: "member"`，而后端枚举仅接受 `viewer`/`editor`/`admin`。修复在 UI 引入 `selectedRole` 状态并以 `Select` 下拉选择。资料来源：[app/main.py:1-80]()

### 4.5 MCP / Claude 连接异常

issue #7 反映出 Windows 上通过 `node.exe` 启动 `mcp-remote` 时路径含空格被错误解析。建议：将 `C:\Program Files (x86)` 改用 8.3 短名，或在 JSON 中显式转义；同时确认 Arkon 侧已正确读取 `ANTHROPIC_API_KEY`。

## 五、运维建议

1. **迁移前备份**：Alembic 升级前对 PostgreSQL 做 `pg_dump`，避免不可逆 DDL。
2. **环境分离**：`.env.docker` 与本地 `.env` 不要混用，防止端口与凭据冲突。
3. **日志聚焦**：API 崩溃时优先查看 `entrypoint.sh` 中迁移阶段与 router 导入阶段的输出。
4. **自托管 LLM 规划**：issue #19 建议未来支持 Gemma/Qwen 替代商业模型，运维侧需预留 GPU 节点与独立模型路由配置。
5. **权限粒度**：issue #11 提出跨部门细粒度授权诉求，部署时可预先规划 `Scope`/`Action`/`ScopeRole` 三张表的租户隔离策略。

> 本页内容综合 Dockerfile、docker-compose.yml、entrypoint.sh、Alembic 配置与社区 issue 反馈整理而成，便于在新环境首次部署或升级时快速对照排查。

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Doramagic 踩坑日志

项目：nduckmink/arkon

摘要：发现 13 个潜在踩坑项，其中 0 个为 high/blocking；最高优先级：安装坑 - 来源证据：Creating a wiki page fails with 500 — page_type hybrid_property has no setter。

## 1. 安装坑 · 来源证据：Creating a wiki page fails with 500 — page_type hybrid_property has no setter

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安装相关的待验证问题：Creating a wiki page fails with 500 — page_type hybrid_property has no setter
- 对用户的影响：可能影响升级、迁移或版本选择。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/20 | 来源讨论提到 python 相关条件，需在安装/试用前复核。

## 2. 安装坑 · 来源证据：Document indexing fails on NUL byte (0x00) in extracted text — CharacterNotInRepertoireError

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安装相关的待验证问题：Document indexing fails on NUL byte (0x00) in extracted text — CharacterNotInRepertoireError
- 对用户的影响：可能影响升级、迁移或版本选择。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/21 | 来源讨论提到 python 相关条件，需在安装/试用前复核。

## 3. 配置坑 · 可能修改宿主 AI 配置

- 严重度：medium
- 证据强度：source_linked
- 发现：项目面向 Claude/Cursor/Codex/Gemini/OpenCode 等宿主，或安装命令涉及用户配置目录。
- 对用户的影响：安装可能改变本机 AI 工具行为，用户需要知道写入位置和回滚方法。
- 证据：capability.host_targets | https://github.com/nduckmink/arkon | host_targets=mcp_host, claude

## 4. 能力坑 · 能力判断依赖假设

- 严重度：medium
- 证据强度：source_linked
- 发现：README/documentation is current enough for a first validation pass.
- 对用户的影响：假设不成立时，用户拿不到承诺的能力。
- 证据：capability.assumptions | https://github.com/nduckmink/arkon | README/documentation is current enough for a first validation pass.

## 5. 维护坑 · 维护活跃度未知

- 严重度：medium
- 证据强度：source_linked
- 发现：未记录 last_activity_observed。
- 对用户的影响：新项目、停更项目和活跃项目会被混在一起，推荐信任度下降。
- 证据：evidence.maintainer_signals | https://github.com/nduckmink/arkon | last_activity_observed missing

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 证据：downstream_validation.risk_items | https://github.com/nduckmink/arkon | no_demo; severity=medium

## 7. 安全/权限坑 · 存在评分风险

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 对用户的影响：风险会影响是否适合普通用户安装。
- 证据：risks.scoring_risks | https://github.com/nduckmink/arkon | no_demo; severity=medium

## 8. 安全/权限坑 · 来源证据：API crash loop on fresh clone: scopes router imports undefined names (Action, ScopeMembership, ScopeRole) from app.data…

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：API crash loop on fresh clone: scopes router imports undefined names (Action, ScopeMembership, ScopeRole) from app.database.models
- 对用户的影响：可能阻塞安装或首次运行。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/10 | 来源讨论提到 python 相关条件，需在安装/试用前复核。

## 9. 安全/权限坑 · 来源证据：Lỗi không kết nối đc claude

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Lỗi không kết nối đc claude
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/7 | 来源讨论提到 node 相关条件，需在安装/试用前复核。

## 10. 安全/权限坑 · 来源证据：MarketNow — MCP skill marketplace with trust layer (8,560 skills, Sentinel L2, x402)

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：MarketNow — MCP skill marketplace with trust layer (8,560 skills, Sentinel L2, x402)
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/22 | 来源讨论提到 npm 相关条件，需在安装/试用前复核。

## 11. 安全/权限坑 · 来源证据：không thể login được

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：không thể login được
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/nduckmink/arkon/issues/6 | 来源讨论提到 python 相关条件，需在安装/试用前复核。

## 12. 维护坑 · issue/PR 响应质量未知

- 严重度：low
- 证据强度：source_linked
- 发现：issue_or_pr_quality=unknown。
- 对用户的影响：用户无法判断遇到问题后是否有人维护。
- 证据：evidence.maintainer_signals | https://github.com/nduckmink/arkon | issue_or_pr_quality=unknown

## 13. 维护坑 · 发布节奏不明确

- 严重度：low
- 证据强度：source_linked
- 发现：release_recency=unknown。
- 对用户的影响：安装命令和文档可能落后于代码，用户踩坑概率升高。
- 证据：evidence.maintainer_signals | https://github.com/nduckmink/arkon | release_recency=unknown

<!-- canonical_name: nduckmink/arkon; human_manual_source: deepwiki_human_wiki -->
