diff --git a/apps/docs/next-env.d.ts b/apps/docs/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/apps/docs/next-env.d.ts
+++ b/apps/docs/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/content/docs/build/automation/approvals.zh-Hans.mdx b/content/docs/build/automation/approvals.zh-Hans.mdx
new file mode 100644
index 0000000..ae1a591
--- /dev/null
+++ b/content/docs/build/automation/approvals.zh-Hans.mdx
@@ -0,0 +1,143 @@
+---
+title: 审批流程
+description: 把记录路由给人签核 —— 同时不让自动化悄悄绕过行级安全。
+---
+
+# 审批流程
+
+**审批就是带审批节点的[流程](/docs/build/automation/flows):流程暂停,直到有人批准或拒绝,然后沿匹配的分支继续。**没有需要另学的独立审批引擎 —— 触发器、分支、错误处理都与任何其他流程完全一致。本页新增的内容是审批节点本身,以及两个与路由同样重要的访问决策。
+
+## 谁做什么
+
+三个角色,刻意分离:
+
+| 角色 | 能做 | 需要 |
+|---|---|---|
+| **构建者** | 编写和编辑审批流程 | `manage_metadata`(通常是 Studio 用户) |
+| **提交人** | 提交进入流程的记录 | 正常的记录访问 —— 不需要任何自动化配置权限 |
+| **审批人** | 处理审批请求(批准 / 拒绝) | 由其权限集授予的能力 |
+
+最终用户提交记录、处理请求;他们从不编辑自动化。把自动化配置界面挡在消费者应用之外。
+
+## 以谁的身份运行 —— 安全决策
+
+流程通过 `runAs` 声明运行身份,对审批而言,正是这个决策防止流程悄悄绕过行级安全:
+
+| `runAs` | 数据操作以谁运行 | 何时使用 |
+|---|---|---|
+| `'user'`(默认) | **提交人**,遵守其 RLS | 流程只触碰提交人本来就能看到的记录 |
+| `'system'` | **提权** —— 绕过 RLS | 流程必须读写提交人看不到的记录(写入总账、通知一个拥有提交人不可见行的审批人) |
+
+**显式**声明提权,让它可见而非意外。默认的 `'user'` 意味着审批流程无法悄悄给提交人跨租户或跨所有者的触达 —— 提权是自愿开启且可审计的。
+
+> **提示:**由定时触发的升级流程没有触发用户 —— 它必须 `runAs: 'system'` 才能行动。这是提权的正当场景;"到处 `system` 好让它能跑"才是反模式。
+
+## 审批节点
+
+节点声明**谁来审批**以及多个决策如何聚合:
+
+```ts
+{
+ id: 'manager_approval',
+ type: 'approval',
+ label: 'Manager Approval',
+ config: {
+ approvers: [{ type: 'field', value: 'owner_manager_id' }],
+ behavior: 'unanimous',
+ approvalStatusField: 'approval_status',
+ lockRecord: true,
+ },
+}
+```
+
+| 配置 | 作用 |
+|---|---|
+| `approvers` | 谁必须决策 —— 具名用户、岗位,或从记录字段解析出的用户(如提交人的经理) |
+| `behavior` | 多个审批人如何聚合,如 `'unanimous'`(一致同意) |
+| `approvalStatusField` | 可选的记录字段,插件把请求状态镜像进去 |
+| `lockRecord` | 请求待定期间锁定记录、禁止编辑 |
+
+> **警告 —— `position` vs `role`。**`{ type: 'position', value: 'finance_manager' }` 路由给某个岗位的持有者(`sys_user_position`)。而 `role` 审批人类型是组织成员层级(`sys_member.role`:`owner`/`admin`/`member`)—— 把岗位名写成 `type: 'role'` 谁也匹配不上,请求就会卡住。`os lint` 会标记这个问题(`approval-role-not-membership-tier`)。
+
+## 一个完整的审批流程
+
+把大额提案路由给负责人的经理,再按决策分支:
+
+```ts
+export const opportunityApproval = defineFlow({
+ name: 'opportunity_approval',
+ label: 'Opportunity Approval',
+ type: 'record_change',
+ status: 'active',
+ runAs: 'user', // 用提交人的 RLS,除非某一步确实需要更多权限
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ config: {
+ triggerType: 'record-after-update',
+ objectName: 'opportunity',
+ condition: "record.amount >= 50000 && record.stage == 'proposal'",
+ },
+ },
+ {
+ id: 'manager_approval',
+ type: 'approval',
+ label: 'Manager Approval',
+ config: {
+ approvers: [{ type: 'field', value: 'owner_manager_id' }],
+ behavior: 'unanimous',
+ approvalStatusField: 'approval_status',
+ lockRecord: true,
+ },
+ },
+ { id: 'mark_approved', type: 'update_record', label: 'Mark Approved' },
+ { id: 'mark_rejected', type: 'update_record', label: 'Mark Rejected' },
+ { id: 'end', type: 'end' },
+ ],
+ edges: [
+ { id: 'e1', source: 'start', target: 'manager_approval' },
+ { id: 'approved', source: 'manager_approval', target: 'mark_approved', label: 'approve' },
+ { id: 'rejected', source: 'manager_approval', target: 'mark_rejected', label: 'reject' },
+ { id: 'e4', source: 'mark_approved', target: 'end' },
+ { id: 'e5', source: 'mark_rejected', target: 'end' },
+ ],
+});
+```
+
+注意带标签的边:`approve` 和 `reject` 命名了决策之后的分支。永远要建模拒绝路径 —— 只有批准分支的流程会让被拒绝的记录搁浅。
+
+**多级审批:**串联多个 `approval` 节点。**并行审批:**见[流程](/docs/build/automation/flows)中的聚合节点模式。
+
+## 审批人体验到什么
+
+`@objectstack/plugin-approvals` 包持有持久化的审批状态。请求待定期间:
+
+1. 插件持久化请求(`sys_approval_request`)以及针对它的每个决策(`sys_approval_action`)—— 你的审批历史是可查询的数据。
+2. 设置 `lockRecord: true` 时,记录被锁定、禁止编辑,直到决策落地。
+3. 如果设置了 `approvalStatusField`,记录自身的字段会镜像请求状态,视图和报表就能按它筛选。
+4. 审批人批准或拒绝;插件沿匹配的边恢复被暂停的流程。
+
+批准本身也是被门控的操作。把"可以批准"建模为一个能力(如 `approve_invoice`),由审批人的权限集授予,并把批准操作的 `requiredPermissions` 建立在它之上 —— 这样门控就在 **UI 和服务端**两侧同时强制,而不只是从屏幕上藏起来。
+
+## 最佳实践
+
+| 应该 | 不应该 |
+|---|---|
+| 定义清晰的进入条件 | 设置过多的审批环节 |
+| 设置合理的超时时间 | 把审批做得过于复杂 |
+| 在合适的场景允许撤回 | 忘记拒绝路径 |
+| 通知所有相关方 | 把审批人写死 |
+| 追踪审批历史 | 只在 UI 层门控"批准" |
+| 默认 `runAs: 'user'`,一步一步地提权 | 到处设置 `runAs: 'system'` "好让它能跑" |
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [流程](/docs/build/automation/flows) | 本页所基于的流程参考 —— 触发器、步骤、错误处理 |
+| [工作流](/docs/build/automation/workflows) | 约束审批所处的生命周期 |
+| [操作](/docs/build/interface/actions) | 把提交和批准做成界面上的按钮 |
+| [CEL 表达式](/docs/reference/cel) | 进入条件背后的语言 |
+| [Email](/docs/configure/email) | 通知审批人与相关方 |
+| [自动化总览](/docs/build/automation) | 选择器:流程 vs 工作流 vs 审批 |
diff --git a/content/docs/build/automation/index.zh-Hans.mdx b/content/docs/build/automation/index.zh-Hans.mdx
new file mode 100644
index 0000000..ff2e176
--- /dev/null
+++ b/content/docs/build/automation/index.zh-Hans.mdx
@@ -0,0 +1,39 @@
+---
+title: 自动化
+description: 为任务选对工具 —— 流程管步骤,工作流管状态,审批管人工签核。
+---
+
+# 自动化
+
+**自动化以声明的方式把业务逻辑挂到数据模型上 —— 作为由运行时执行的元数据 —— 而不是散落在应用代码里。**三个工具覆盖全部场景,一开始就选对,能省去日后返工。
+
+## 我该用哪一个?
+
+| 你需要 | 用 | 阅读 |
+|---|---|---|
+| "X 发生时,做 Y" —— 由记录变更、定时或按钮点击驱动的步骤 | **流程** | [流程](/docs/build/automation/flows) |
+| "这条记录只能按这些事件在这些状态间移动" —— 受控的生命周期 | **工作流**(状态机) | [工作流](/docs/build/automation/workflows) |
+| "必须有人签核后才能继续" —— 路由给人的决策 | **审批** | [审批流程](/docs/build/automation/approvals) |
+
+经验法则:**状态**用工作流建模,**步骤**用流程建模。审批不是独立引擎 —— 审批就是在审批节点暂停、等人决策的流程。
+
+## 它们如何组合
+
+三者是互补而非竞争:
+
+- **工作流**约束哪些生命周期迁移是合法的 —— 状态、迁移、守卫条件,仅此而已。
+- **流程**执行这些迁移周边的副作用:发邮件、更新记录、调用外部服务、等待、分支。
+- 流程内的**审批节点**阻塞执行直到审批人行动,然后沿批准或拒绝分支继续。
+
+所以"工单从 new → assigned → resolved"是工作流;"升级时通知经理"是流程;"超过 5 万美元的折扣需要财务经理签核"是带审批节点的流程。
+
+> **提示:**如果需求读起来是"X 发生时,做 Y",那就是流程。如果读起来是"这条记录绝不能跳过某个状态",那就是工作流。从这里出发,你几乎不会选错。
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [流程](/docs/build/automation/flows) | 完整的流程参考 —— 触发器、步骤类型、错误处理、CEL |
+| [工作流](/docs/build/automation/workflows) | 严格生命周期的状态、迁移与守卫条件 |
+| [审批流程](/docs/build/automation/approvals) | 审批节点、运行身份与审批人体验 |
+| [CEL 表达式](/docs/reference/cel) | 条件与守卫条件所用的表达式语言 |
diff --git a/content/docs/build/automation/workflows.zh-Hans.mdx b/content/docs/build/automation/workflows.zh-Hans.mdx
new file mode 100644
index 0000000..3d67c5d
--- /dev/null
+++ b/content/docs/build/automation/workflows.zh-Hans.mdx
@@ -0,0 +1,139 @@
+---
+title: 工作流
+description: 把记录的生命周期建模成状态机 —— 合法状态、带守卫条件的迁移,其余的交给流程。
+---
+
+# 工作流
+
+**工作流把记录的生命周期建模为有限状态机:记录可以处于的状态、在状态间移动它的事件,以及移动成立所必须满足的守卫条件。**当核心需求是"这个对象只能按这些事件在这些状态间移动"时,就用它。
+
+不存在独立的 Salesforce 式 Workflow Rule 编写类型。旧的"工作流"概念被干净地一分为二:
+
+- **状态机元数据**负责严格的生命周期迁移 —— 即本页。
+- **[流程](/docs/build/automation/flows)**负责事件触发或定时的自动化,包括[审批](/docs/build/automation/approvals)暂停。
+
+## 工作流 vs 流程
+
+| | 工作流(状态机) | 流程 |
+|---|---|---|
+| 建模 | *状态* —— 记录处于生命周期的哪里 | *步骤* —— 某事发生时做什么 |
+| 回答 | "此刻允许这个迁移吗?" | "我们要对它做什么?" |
+| 形态 | 状态、迁移、守卫条件 | 节点与边:触发器、动作、分支 |
+| 副作用 | 无 —— 它只做约束 | 全都有 —— 邮件、更新、HTTP、等待 |
+
+两者组合使用:状态机约束迁移;当你需要通知、更新记录或调用外部系统时,由流程执行迁移周边的副作用。
+
+## 定义状态机
+
+一个必须按 `new → assigned → resolved` 移动、且带升级路径的支持工单:
+
+```ts
+import type { StateMachineConfig } from '@objectstack/spec/automation';
+
+export const caseLifecycle: StateMachineConfig = {
+ id: 'case_lifecycle',
+ initial: 'new',
+ states: {
+ new: {
+ on: {
+ ASSIGN: { target: 'assigned' },
+ },
+ },
+ assigned: {
+ on: {
+ RESOLVE: { target: 'resolved', cond: 'has_resolution' },
+ ESCALATE: { target: 'escalated' },
+ },
+ },
+ escalated: {
+ on: {
+ RESOLVE: { target: 'resolved', cond: 'has_resolution' },
+ },
+ },
+ resolved: {
+ type: 'final',
+ },
+ },
+};
+```
+
+把它当契约来读:`new` 状态的工单只能被分派。`assigned` 状态的工单可以被解决 —— 但只有 `has_resolution` 守卫条件通过时 —— 或者被升级。`resolved` 是终态;再没有什么能移动它。
+
+## 解剖
+
+| 键 | 声明什么 |
+|---|---|
+| `id` | 状态机的标识符 |
+| `initial` | 每条新记录的初始状态 |
+| `states` | 状态名 → 其出向迁移的映射 |
+| `on` | 该状态响应的事件(`ASSIGN`、`RESOLVE`……) |
+| `target` | 事件把记录移动到的状态 |
+| `cond` | 迁移触发前必须满足的守卫条件 |
+| `type: 'final'` | 终态 —— 没有出向迁移 |
+
+### 守卫条件
+
+守卫条件(`cond`)让迁移带上前提:上例中,只有当 `has_resolution` 成立时 `RESOLVE` 才能到达 `resolved`。守卫条件把"没有解决方案就不能关单"编码成结构性规则,而不是散落在 UI 代码里的验证。
+
+### 事件,而非字段写入
+
+迁移由具名**事件**(`ASSIGN`、`ESCALATE`)触发,而不是对状态字段的任意编辑。这正是重点:状态机定义了合法移动的完整集合,未声明的一律不可能发生。
+
+> **提示:**保持状态机最小 —— 状态、迁移、守卫条件。一旦你想"然后再发封邮件",你就已经离开了工作流的领地:把副作用放进一个响应该迁移的[流程](/docs/build/automation/flows)。
+
+## 副作用属于流程
+
+状态机只描述合法迁移与守卫条件 —— 别无其他。当某次迁移应该*做*点什么(通知销售、写入关闭日期、调用外部系统)时,给状态机配上一个由记录变更触发的流程:
+
+```ts
+export const dealClosedWon = defineFlow({
+ name: 'deal_closed_won',
+ type: 'record_change',
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ config: {
+ triggerType: 'record-after-update',
+ objectName: 'opportunity',
+ condition: "record.stage == 'closed_won' && previous.stage != 'closed_won'",
+ },
+ },
+ { id: 'set_closed_date', type: 'update_record', label: 'Set Closed Date' },
+ { id: 'notify_sales', type: 'notify', label: 'Notify Sales' },
+ { id: 'end', type: 'end' },
+ ],
+ edges: [
+ { id: 'e1', source: 'start', target: 'set_closed_date' },
+ { id: 'e2', source: 'set_closed_date', target: 'notify_sales' },
+ { id: 'e3', source: 'notify_sales', target: 'end' },
+ ],
+});
+```
+
+状态机保证这笔交易是合法地*到达* `closed_won` 的;流程处理接下来发生的事。上面这样的条件是 CEL 表达式 —— 见 [CEL](/docs/reference/cel)。
+
+## 从 Workflow Rules 迁移
+
+如果你来自有 Workflow Rules 的平台,对照表如下:
+
+| 旧概念 | 当前对应 |
+|---|---|
+| Workflow Rule | 流程 |
+| 时间触发器 | 定时流程 |
+| 字段更新动作 | `update_record` 节点 |
+| 邮件提醒 | `notify` 节点 |
+| HTTP 调用 | `http` 节点 |
+| Approval Process | 带一个或多个 `approval` 节点的流程 |
+
+判断标准:如果旧规则是"X 发生时,做 Y",就建成一个小流程。如果它是"这条记录必须在受控状态间移动",就把生命周期建成状态机,副作用交给流程。
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [流程](/docs/build/automation/flows) | 迁移周边的副作用 —— 触发器、步骤、错误处理 |
+| [审批流程](/docs/build/automation/approvals) | 作为流程内暂停的人工签核 |
+| [CEL 表达式](/docs/reference/cel) | 守卫条件与流程条件背后的语言 |
+| [数据建模](/docs/build/data) | 你正在约束其生命周期的对象 |
+| [自动化总览](/docs/build/automation) | 选择器:流程 vs 工作流 vs 审批 |
diff --git a/content/docs/build/data/formulas.zh-Hans.mdx b/content/docs/build/data/formulas.zh-Hans.mdx
new file mode 100644
index 0000000..795aced
--- /dev/null
+++ b/content/docs/build/data/formulas.zh-Hans.mdx
@@ -0,0 +1,194 @@
+---
+title: 公式
+description: 计算字段、动态默认值与条件逻辑 —— 元数据需要"思考"的地方,都用同一门 CEL 表达式语言。
+---
+
+# 公式
+
+**一门表达式语言,覆盖所有场景。**只要有一段元数据需要计算值或求值条件,ObjectOS 就使用 [CEL](/docs/reference/cel)(谷歌的 Common Expression Language)—— 公式字段、动态默认值、条件可见性、验证条件、流程决策。语法学一次,处处能用。
+
+| 场景 | CEL 在那里做什么 |
+|---|---|
+| **公式字段**(`type: 'formula'`) | 读取时由其他字段计算出一个值 |
+| **动态默认值**(``defaultValue: cel`...` ``) | 在插入时求值,而不是编译时 |
+| **字段谓词**(`visibleWhen`、`readonlyWhen`、`requiredWhen`) | 按条件显示 / 锁定 / 必填某个字段 |
+| **[验证规则](/docs/build/data/validation-rules)**(`condition`) | 标记无效记录 |
+| **[视图](/docs/build/interface/views)**(`visibleOn`、`conditionalFormatting`) | 条件区块与行样式 |
+| **[流程](/docs/build/automation/flows)**(决策、Hook 条件) | 按记录状态分支 |
+
+本页讲构建者如何*使用*公式。完整的语言 —— 运算符、标准库、`Expression` 信封 —— 见 [CEL 参考](/docs/reference/cel)。
+
+## 公式字段
+
+公式字段在读取时由一条 CEL 表达式计算出它的值。它是只读的 —— 永远不能直接写入:
+
+```ts
+import { F } from '@objectstack/spec';
+import { ObjectSchema, Field } from '@objectstack/spec/data';
+
+export const Invoice = ObjectSchema.create({
+ name: 'invoice',
+ fields: {
+ subtotal: Field.decimal({ label: 'Subtotal' }),
+ tax_rate: Field.decimal({ label: 'Tax Rate' }),
+
+ total: Field.formula({
+ label: 'Total',
+ returnType: 'number',
+ expression: F`record.subtotal + record.subtotal * record.tax_rate`,
+ }),
+ },
+});
+```
+
+两个关键的键:
+
+| 键 | 作用 |
+|---|---|
+| `expression` | CEL 源码 —— 运行时**唯一**求值的键 |
+| `returnType` | 公式返回什么:`'number'`、`'text'`、`'boolean'`、`'date'` —— 供仪表盘与格式化读取 |
+
+> **警告:**公式字段的 `type` 永远是 `formula`。**不要**给 `Field.formula` 传 `type: 'currency'` 或 `type: 'number'` —— 那会覆盖 `type: 'formula'`,字段就静默地永远不再计算。值的类型请用 `returnType` 声明。
+
+### 日常模式
+
+```ts
+// 分档标签
+Field.formula({
+ label: 'Discount Tier',
+ returnType: 'text',
+ expression: `
+ record.amount > 1000000 ? "Platinum" :
+ record.amount > 500000 ? "Gold" :
+ record.amount > 100000 ? "Silver" : "Bronze"
+ `,
+})
+
+// 距关闭还有几天
+Field.formula({
+ label: 'Days to Close',
+ returnType: 'number',
+ expression: 'daysBetween(record.close_date, today())',
+})
+
+// 全名 —— joinNonEmpty 会跳过空白部分
+Field.formula({
+ label: 'Full Name',
+ returnType: 'text',
+ expression: F`joinNonEmpty([record.salutation, record.first_name, record.last_name], ' ')`,
+})
+
+// 毛利率,保留 2 位小数
+Field.formula({
+ label: 'Gross Margin %',
+ returnType: 'number',
+ expression: 'record.revenue > 0 ? ((record.revenue - record.cost) / record.revenue) * 100 : 0',
+ scale: 2,
+})
+```
+
+> **提示:**CEL 遇到 `null + string` 会抛错,所以拼接时把可能为空的操作数包进 `coalesce(record.x, '')` —— 或者直接用 `joinNonEmpty`,省掉这套仪式。
+
+### 公式作为记录标题
+
+自 ADR-0079 起,记录的标题由 `nameField` 指定的字段决定 —— 组合式标题可以用文本公式:
+
+```ts
+ObjectSchema.create({
+ name: 'invoice',
+ nameField: 'display_title',
+ fields: {
+ display_title: Field.formula({
+ returnType: 'text',
+ expression: F`"Invoice " + string(record.invoice_no) + " – " + record.customer.name`,
+ }),
+ },
+});
+```
+
+## 动态默认值
+
+包在 `` cel`...` `` 里的 `defaultValue` 在**插入时**求值 —— 用的是用户的时钟和身份,不是你笔记本的:
+
+```ts
+import { cel } from '@objectstack/spec';
+
+issued_date: Field.date({
+ defaultValue: cel`today()`,
+}),
+due_date: Field.date({
+ defaultValue: cel`daysFromNow(30)`,
+}),
+```
+
+同样的技巧也适用于种子数据:一条带 ``close_date: cel`daysFromNow(45)` `` 的种子记录会在包安装时解析,因此演示数据永远新鲜,而不是把编译时的时间戳固化进包里。
+
+## 条件式字段行为
+
+谓词让字段随记录的其余部分而变化:
+
+```ts
+import { P } from '@objectstack/spec';
+
+po_number: Field.text({
+ label: 'PO Number',
+ requiredWhen: P`record.amount > 10000`,
+}),
+rating: Field.select({
+ label: 'Rating',
+ options: [ /* ... */ ],
+ visibleWhen: P`record.status == 'qualified'`,
+}),
+notes: Field.textarea({
+ readonlyWhen: P`record.status == 'approved'`,
+}),
+```
+
+`F`(公式)、`P`(谓词)、`cel` 这几个标签模板 helper 可以互换 —— 在调用处哪个读起来顺就用哪个。
+
+## 30 秒学会 CEL
+
+| 概念 | 语法 |
+|---|---|
+| 当前记录的字段 | `record.amount` |
+| 更新前的值 | `previous.status` |
+| 相等 / 逻辑 | `==` `!=` `&&` `\|\|` `!` |
+| 条件表达式 | `cond ? then : else` |
+| 成员判断 | `record.region in ['us', 'eu']` |
+| 空白检查 | `isBlank(record.phone)` |
+| 日期 | `today()`、`now()`、`daysBetween(a, b)`、`addDays(d, n)` |
+| 字符串 | `upper`、`lower`、`trim`、`contains`、`matches` |
+
+完整的运算符表与标准库:[CEL 参考](/docs/reference/cel)。
+
+> **警告:**谓词是裸 CEL —— 千万别把字段引用包进花括号。`{record.rating} >= 4` 是一个 **map 字面量**,会导致解析错误;应写成 `record.rating >= 4`。花括号只属于 `{{ … }}` 文本模板。畸形的表达式会导致构建失败并在运行时抛错 —— 它绝不会静默地求值为 `false`。
+
+> **提示:**`has(record.x)` 只要键存在就为 true —— 哪怕值是 `null`。要检查"非空白",用 `isBlank(...)` 或 `record.x != null`。
+
+## 最佳实践
+
+| 应该 | 不应该 |
+|---|---|
+| 保持公式简洁易读 | 制造循环引用 |
+| 测试边界情况(null、零、空) | 把三元表达式嵌套五层深 |
+| 用 `coalesce` / `isBlank` 处理空值 | 把公式用在频繁变化的数据上 |
+| 给每个公式声明 `returnType` | 重新实现本该由验证规则负责的逻辑 |
+
+## 用对话构建
+
+你很少需要手写公式。告诉 [AI Builder](/docs/build/ai-builder):
+
+> *"在 opportunity 上加一个 Days to Close 公式:close_date 与今天之间的天数。"*
+
+它会生成 CEL,推断并写好 `returnType`,表达式在发布前会先经过校验 —— 无效的公式会带着定位信息使构建失败。
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [CEL 参考](/docs/reference/cel) | 完整的语言:运算符、标准库、表达式信封 |
+| [数据模型](/docs/build/data) | 公式字段所在的地方 |
+| [验证规则](/docs/build/data/validation-rules) | 用 CEL 条件拦截坏写入 |
+| [视图](/docs/build/interface/views) | `visibleOn` 与 `conditionalFormatting` 中的 CEL |
+| [流程](/docs/build/automation/flows) | 决策与 Hook 条件中的 CEL |
+| [字段类型](/docs/reference/field-types) | `formula`、`summary`、`autonumber` 等 |
diff --git a/content/docs/build/data/relationships.zh-Hans.mdx b/content/docs/build/data/relationships.zh-Hans.mdx
new file mode 100644
index 0000000..99aef8e
--- /dev/null
+++ b/content/docs/build/data/relationships.zh-Hans.mdx
@@ -0,0 +1,167 @@
+---
+title: 关系
+description: 用查找(lookup)与主从(master-detail)连接对象 —— 级联规则、过滤选择器、层级结构、连接对象与汇总字段。
+---
+
+# 关系
+
+**关系就是一个字段。**把 `lookup` 或 `masterDetail` 字段指向另一个对象,ObjectOS 会把上层的一切都接好:外键完整性、Console 里的记录选择器、父记录页面上的相关列表,以及查询中的 `expand`。
+
+| 类型 | 基数 | 删除默认行为 | 用于 |
+|---|---|---|---|
+| `lookup` | 多对一 | `set_null` | 松散引用(工单 → 负责人) |
+| `masterDetail` | 多对一、有归属 | `cascade` | 父子归属(订单 → 明细行) |
+| `tree` | 自引用 | — | 层级结构(分类、组织架构图) |
+| `user` | 多对一 | `set_null` | 人员选择器 —— 特化为 `sys_user` 的查找 |
+| `summary` | 汇总到父记录 | — | 聚合子记录(sum、count、avg) |
+
+## 查找(多对一)
+
+主力字段。引用另一个对象中的一条记录:
+
+```ts
+import { ObjectSchema, Field } from '@objectstack/spec/data';
+
+export const Contact = ObjectSchema.create({
+ name: 'contact',
+ fields: {
+ account: Field.lookup('account', {
+ label: 'Account',
+ required: true,
+ }),
+ },
+});
+```
+
+平台会校验被引用的记录确实存在 —— 每次写入都强制外键完整性。
+
+> **提示:**按所指向的对象给字段命名 —— 用 `account`,而不是 `account_id`。这样相关列表、`expand` 和 AI Builder 读起来都更顺。
+
+### 父记录被删除时会发生什么
+
+用 `deleteBehavior` 控制级联行为:
+
+| 值 | 父记录删除时 |
+|---|---|
+| `set_null` | 子记录的引用被清空(lookup 默认) |
+| `cascade` | 子记录也一并删除 |
+| `restrict` | 只要还有子记录,就阻止删除 |
+
+## 过滤查找
+
+约束选择器提供哪些记录。静态条件用 `lookupFilters`;用 `dependsOn` 让候选记录随同一记录上另一个字段的取值收窄:
+
+```ts
+contact: Field.lookup('contact', {
+ label: 'Contact',
+ dependsOn: ['account'], // 只显示所选 account 下的联系人
+ lookupFilters: [
+ { field: 'is_active', operator: 'eq', value: true },
+ ],
+})
+```
+
+> **警告:**旧的 `referenceFilters: string[]` 属性(如 `['is_active = true']`)虽然能通过 Schema 校验,但**记录选择器 UI 并不读取它** —— 它什么都不过滤。请始终使用上面展示的结构化 `lookupFilters` + `dependsOn`。
+
+## 主从(有归属的子记录)
+
+`masterDetail` 是带归属语义的查找:子记录属于父记录,默认随父级联删除,并且可以在父表单上以明细行的形式内联编辑。
+
+```ts
+export const OrderLine = ObjectSchema.create({
+ name: 'order_line',
+ fields: {
+ order: Field.masterDetail({ reference: 'order', label: 'Order' }),
+ product: Field.lookup('product', { label: 'Product' }),
+ quantity: Field.number({ label: 'Quantity', min: 1 }),
+ },
+});
+```
+
+| 方面 | 行为 |
+|---|---|
+| 删除 | 运行时级联到子记录,除非 `deleteBehavior: 'restrict'` |
+| 归属 | 子记录归父记录所有 |
+| 编辑 | 可选:在父表单上内联编辑明细行 |
+| 汇总 | `summary` 字段只在主(master)侧有效 |
+
+引用是可选或共享的,用 `lookup`;子记录离开父记录就没有意义的,用 `masterDetail`。
+
+## 自引用查找与树
+
+查找可以指回自己的对象,用来构建层级结构:
+
+```ts
+parent_account: Field.lookup('account', {
+ label: 'Parent Account',
+ description: 'Parent company in hierarchy',
+})
+```
+
+对于专门的层级结构(分类、组织架构图),使用 `tree` 字段类型 —— 一个存储和展开方式与 `lookup` 相同的自引用查找。注意引擎在写入时**不做**环路检查,所以一条绕回自身的链不会被自动拒绝。
+
+> **提示:**把 `tree` / 自引用查找字段与[树形列表视图](/docs/build/interface/views)搭配使用,即可在 Console 中把层级渲染为嵌套行。
+
+## 相关列表
+
+这些是免费得到的。当查找指向父对象时,子记录会自动以相关列表的形式出现在父记录的详情页上:
+
+- Contact 有 `account: Field.lookup('account')`
+- → Account 详情页会显示一个 **Contacts** 相关列表
+
+无需任何额外配置。
+
+## 多对多:连接对象
+
+ObjectOS 没有直接的多对多字段 —— 用**连接对象**来建模:一个带两个关系字段的对象,各指向一侧。
+
+```ts
+// Student ↔ Course,通过 Enrollment 连接
+export const Enrollment = ObjectSchema.create({
+ name: 'enrollment',
+ fields: {
+ student: Field.masterDetail({ reference: 'student', label: 'Student' }),
+ course: Field.masterDetail({ reference: 'course', label: 'Course' }),
+ grade: Field.select({ label: 'Grade', options: [ /* ... */ ] }),
+ },
+ indexes: [
+ { fields: ['student', 'course'], unique: true }, // 每对组合只允许一条报名记录
+ ],
+});
+```
+
+两侧都会把连接记录看作一个相关列表,而连接对象本身正是存放这段配对属性(成绩、角色、加入日期)的天然位置。
+
+## 汇总(`summary` 字段)
+
+用 `summary` 字段把子记录聚合到父记录上。只在主对象上有效 —— 即主从关系的父侧:
+
+```ts
+// 在 Order 对象上
+total_lines: {
+ type: 'summary',
+ label: 'Line Count',
+ summaryOperations: {
+ object: 'order_line', // 要聚合的子对象
+ field: 'quantity', // 要聚合的字段
+ function: 'sum', // count | sum | min | max | avg
+ },
+}
+```
+
+Summary 字段是只读的,由子记录计算得出 —— 你永远不会直接写它。
+
+## 跨关系查询
+
+用 `expand` 在一次调用里加载关联记录 —— 查询 API 会顺着查找字段把被引用的记录内联进来,客户端不必发出 N+1 次请求。
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [数据模型](/docs/build/data) | 这些关系所在的对象、字段与 Schema |
+| [公式](/docs/build/data/formulas) | 用 CEL 在记录内计算值 |
+| [验证规则](/docs/build/data/validation-rules) | 跨字段规则与唯一约束 |
+| [视图](/docs/build/interface/views) | 树形视图、Kanban(看板)与相关列表界面 |
+| [字段类型](/docs/reference/field-types) | `lookup`、`master_detail`、`tree`、`summary` 的完整参考 |
+| [AI Builder](/docs/build/ai-builder) | 在对话中描述关系,让平台自动接线 |
diff --git a/content/docs/build/data/validation-rules.zh-Hans.mdx b/content/docs/build/data/validation-rules.zh-Hans.mdx
new file mode 100644
index 0000000..64fe4cf
--- /dev/null
+++ b/content/docs/build/data/validation-rules.zh-Hans.mdx
@@ -0,0 +1,198 @@
+---
+title: 验证规则
+description: 必填字段、唯一约束与 CEL 驱动的规则,在平台层拦住坏数据 —— 错误消息由你掌控。
+---
+
+# 验证规则
+
+**验证在每条写入路径上都会执行。**REST、Console 表单、ObjectQL —— 同一套规则处处生效,坏数据没有后门可钻。验证规则是作用于单条记录的确定性、同步、无副作用谓词:仅凭这次写入(更新时再加上更新前的记录)即可判定,不做任何 I/O。
+
+验证是分层的 —— 能表达规则的最低层就是该用的层:
+
+| 层 | 表达什么 | 示例 |
+|---|---|---|
+| **字段修饰符** | 是否必填、是否唯一 | `required: true`、`unique: true` |
+| **字段约束** | 单字段形态 | `min`、`max`、`maxLength`、`format` |
+| **条件修饰符** | 依赖上下文的必填 | ``requiredWhen: P`record.amount > 10000` `` |
+| **对象级验证规则** | 跨字段业务逻辑 | "折扣不能超过总额" |
+| **唯一索引** | 组合 / 限定范围的唯一性 | `{ fields: ['code', 'organization'], unique: true }` |
+| **生命周期 Hook** | 任意验证代码 | `beforeInsert` / `beforeUpdate` |
+
+## 字段级:required、unique 与约束
+
+通用修饰符适用于所有字段类型:
+
+```ts
+import { ObjectSchema, Field } from '@objectstack/spec/data';
+
+fields: {
+ email: Field.email({ label: 'Contact Email', required: true, unique: true }),
+ quantity: Field.number({ label: 'Quantity', min: 1, max: 9999 }),
+ code: Field.text({ label: 'Code', minLength: 3, maxLength: 20 }),
+}
+```
+
+| 修饰符 | 行为 |
+|---|---|
+| `required: true` | 在运行时拒绝 `null` / `undefined` |
+| `unique: true` | 数据库层唯一约束 —— 而不是有竞态的应用层检查 |
+| `min` / `max` | 数值范围(number、currency、percent、rating、slider……) |
+| `minLength` / `maxLength` | 文本类型的字符长度上下限 |
+| `format` | 内置形态检查 —— `email`、`url`、`phone` 字段类型默认自带 |
+| `readonly: true` | 更新时由服务端强制 —— 非系统写入该字段会被静默丢弃(插入不受限) |
+
+字段类型本身也自带免费验证:`email` 检查 `local@domain` 形态,`url` 要求带协议,`select` 的值必须匹配某个选项,`lookup` 强制被引用记录存在,`json` 必须能解析。每种默认行为见[字段类型参考](/docs/reference/field-types)。
+
+### 条件式必填 / 只读 / 可见
+
+是否必填可以通过 CEL 谓词依赖记录的其余部分:
+
+```ts
+import { P } from '@objectstack/spec';
+
+po_number: Field.text({
+ label: 'PO Number',
+ requiredWhen: P`record.amount > 10000`,
+}),
+```
+
+`visibleWhen`、`readonlyWhen`、`requiredWhen` 都接受一个 CEL 谓词 —— 见[公式](/docs/build/data/formulas)。
+
+## 对象级验证规则
+
+跨字段业务逻辑放在对象的 `validations` 数组里:
+
+```ts
+export const Order = ObjectSchema.create({
+ name: 'order',
+ fields: {
+ amount: Field.currency({ label: 'Amount', required: true }),
+ status: Field.select({ label: 'Status', options: [ /* ... */ ] }),
+ },
+
+ validations: [
+ {
+ name: 'amount_positive',
+ type: 'script',
+ severity: 'error',
+ message: 'Amount must be greater than zero',
+ // CEL 谓词 —— TRUE 表示记录无效。
+ condition: 'record.amount <= 0',
+ events: ['insert', 'update'],
+ },
+ ],
+});
+```
+
+> **警告:**对 `script` 规则来说,`condition` 是**失败**谓词 —— 它求值为 TRUE 时,验证失败。要针对*坏*状态来写判断:`record.amount <= 0` 拒绝非正数金额。
+
+### 公共属性
+
+每种规则类型都共享这套基础形态:
+
+| 属性 | 必填 | 说明 |
+|---|---|---|
+| `name` | 是 | 唯一的规则名(`snake_case`) |
+| `message` | 是 | 面向用户的错误消息 |
+| `type` | 是 | `script`、`state_machine`、`format`、`cross_field`、`json_schema`、`conditional` |
+| `severity` | 否 | `error`(阻止保存,默认)、`warning`(允许保存)、`info` |
+| `events` | 否 | `insert`、`update`、`delete` —— 默认 `['insert', 'update']` |
+| `priority` | 否 | 0–9999,数字越小越先执行(默认 100) |
+| `active` | 否 | 不删除即可开关(默认 `true`) |
+
+### 六种规则类型
+
+| 类型 | 检查什么 | 关键配置 |
+|---|---|---|
+| `script` | 任意 CEL 谓词 | `condition`(TRUE = 无效) |
+| `state_machine` | 允许的状态迁移 | `field`、`transitions` 映射 |
+| `format` | 单字段匹配正则或内置格式 | `field`、`regex` 或 `format: 'email' \| 'url' \| 'phone' \| 'json'` |
+| `cross_field` | 字段之间的关系 | `fields`、`condition` |
+| `json_schema` | JSON 字段匹配 JSON Schema | `field`、`schema` |
+| `conditional` | 仅在谓词成立时应用嵌套规则 | `when`、`then`、可选 `otherwise` |
+
+两条你会反复用到的规则:
+
+```ts
+// 状态机 —— 强制状态流转
+{
+ name: 'order_status_transitions',
+ type: 'state_machine',
+ severity: 'error',
+ message: 'Invalid status transition',
+ field: 'status',
+ transitions: {
+ draft: ['submitted', 'cancelled'],
+ submitted: ['approved', 'rejected', 'cancelled'],
+ approved: ['completed'],
+ rejected: ['draft'],
+ cancelled: [],
+ completed: [],
+ },
+ events: ['update'],
+}
+
+// 跨字段 —— 日期先后有序
+{
+ name: 'date_range_valid',
+ type: 'cross_field',
+ severity: 'error',
+ message: 'End date must be after start date',
+ fields: ['start_date', 'end_date'],
+ condition: 'record.end_date <= record.start_date',
+ events: ['insert', 'update'],
+}
+```
+
+在更新时的条件里,`previous` 持有变更前的快照 —— `record.stage != previous.stage` 可检测到变更。
+
+## 唯一性:用索引,不用规则
+
+**没有** `uniqueness` 验证类型 —— 这是有意为之。先 SELECT 再 INSERT 的检查天然有竞态(TOCTOU);数据库唯一约束没有。唯一性要在数据层强制:
+
+```ts
+// 字段级
+email: Field.email({ label: 'Contact Email', unique: true }),
+
+// 组合 / 限定范围的唯一性用索引
+indexes: [
+ { fields: ['code', 'organization'], unique: true },
+ // 需要限定范围 / 条件约束时加 `partial`
+]
+```
+
+同样的逻辑也排除了异步 / 远程验证(那是客户端表单的关注点,放在写路径上还是 SSRF 与延迟隐患)和自定义处理器 —— 任意验证代码应该放进 `beforeInsert` / `beforeUpdate` 生命周期 Hook。
+
+## 自定义错误消息
+
+`message` 就是规则触发时用户看到的内容 —— 出现在 Console 表单和 API 错误响应中。要写得可执行:
+
+| 弱 | 强 |
+|---|---|
+| "Invalid input" | "Close date is required for closed deals" |
+| "Validation failed" | "Phone must match format: +1-XXX-XXX-XXXX" |
+| "Error" | "Discount cannot exceed total" |
+
+用 `severity` 来校准:`error` 阻止保存,`warning` 显示消息但放行保存,`info` 纯提示。
+
+> **提示:**无法求值的谓词(解析错误、未绑定变量)会被当作损坏的规则处理 —— 记录日志后跳过,而不是拦下每一次写入。
+
+## 最佳实践
+
+| 应该 | 不应该 |
+|---|---|
+| 在尽可能低的层做验证(字段 → 规则 → Hook) | 用 Hook 做字段修饰符就能表达的事 |
+| 写清晰、可执行的消息 | 在多层重复同一个检查 |
+| 用 `priority` 让廉价的格式检查先跑 | 构造过于复杂的条件 |
+| 用真实数据测试规则 | 拦住正当的边界情况 |
+
+## 下一步
+
+| 页面 | 原因 |
+|---|---|
+| [数据模型](/docs/build/data) | 这些规则所保护的对象与字段 |
+| [公式](/docs/build/data/formulas) | `condition` 与 `requiredWhen` 背后的 CEL 语言 |
+| [关系](/docs/build/data/relationships) | 跨对象的引用完整性 |
+| [CEL 参考](/docs/reference/cel) | 条件可用的完整运算符与标准库参考 |
+| [字段类型](/docs/reference/field-types) | 各字段类型的内置验证默认值 |
+| [流程](/docs/build/automation/flows) | 用自动化响应(合法的)记录变更 |
diff --git a/content/docs/build/index.zh-Hans.mdx b/content/docs/build/index.zh-Hans.mdx
index 5a0aa19..eea9bf8 100644
--- a/content/docs/build/index.zh-Hans.mdx
+++ b/content/docs/build/index.zh-Hans.mdx
@@ -23,6 +23,7 @@ description: 在 ObjectOS 里应用是怎么诞生的 —— 跟 AI 对话、在
|---|---|---|
| **[包](/docs/build/packages)** | 组织单位 —— `com.acme.crm`,带版本,可安装 | Packages |
| **[数据模型](/docs/build/data)** | 对象 + 字段 + 关系 + 状态机 | Data Model |
+| **[界面](/docs/build/interface)** | 应用、视图、表单、仪表盘与页面 —— 用户看到的一切 | Interface |
| **[Actions](/docs/build/interface/actions)** | 命名操作,可被 REST、Console 按钮、流程或 AI Agent 调用 | Actions |
| **[流程](/docs/build/automation/flows)** | 声明式业务逻辑(自动启动 / 定时 / 手动) | Flows |
| **[Agents](/docs/build/agents)** | 面向终端用户的 AI 助手 —— Agent → Skill → Tool | Agents |
diff --git a/content/docs/build/interface/actions.zh-Hans.mdx b/content/docs/build/interface/actions.zh-Hans.mdx
index 6f40ef4..dada8d4 100644
--- a/content/docs/build/interface/actions.zh-Hans.mdx
+++ b/content/docs/build/interface/actions.zh-Hans.mdx
@@ -1,9 +1,9 @@
---
-title: Actions
+title: 操作
description: 平台从单一声明出发,将命名操作同时暴露为 REST 端点、Console 按钮、流程步骤和 AI 工具。
---
-# Actions
+# 操作
**Action** 是对象上的一个命名操作。只需声明一次,它就会以以下形式出现:
diff --git a/content/docs/build/interface/apps.zh-Hans.mdx b/content/docs/build/interface/apps.zh-Hans.mdx
new file mode 100644
index 0000000..4e23918
--- /dev/null
+++ b/content/docs/build/interface/apps.zh-Hans.mdx
@@ -0,0 +1,137 @@
+---
+title: 应用与导航
+description: 把对象、视图、页面和仪表盘打包成一个带品牌、可导航的外壳 —— 并精确控制谁能看到什么。
+---
+
+# 应用与导航
+
+**应用**是一个逻辑容器,把对象、视图、页面和仪表盘打包成一体化的体验。它定义导航树、品牌,以及 —— 最关键的 —— 谁能进来。
+
+```ts
+import { App } from '@objectstack/spec/ui'
+
+export const CrmApp = App.create({
+ name: 'crm_app',
+ label: 'CRM',
+ icon: 'briefcase',
+ branding: { primaryColor: '#2563EB' },
+ navigation: [
+ { id: 'group_sales', type: 'group', label: 'Sales', icon: 'briefcase', children: [
+ { id: 'nav_leads', type: 'object', objectName: 'crm_lead', label: 'Leads', icon: 'funnel' },
+ { id: 'nav_accounts', type: 'object', objectName: 'crm_account', label: 'Accounts', icon: 'building' },
+ ]},
+ ],
+ requiredPermissions: ['crm_access'],
+})
+```
+
+## 应用属性
+
+| 属性 | 类型 | 必填 | 说明 |
+|:--|:--|:--|:--|
+| `name` | `string` | 是 | 机器名(`snake_case`) |
+| `label` | `string` | 是 | 显示名 |
+| `icon` | `string` | — | 应用图标(Lucide) |
+| `description` / `version` | `string` | — | 用于列表展示的元数据 |
+| `active` | `boolean` | — | 应用是否激活(默认 `true`) |
+| `isDefault` | `boolean` | — | 是否为默认应用 |
+| `navigation` | `NavigationItem[]` | — | 导航树 |
+| `branding` | `AppBranding` | — | `primaryColor`、`logo`、`favicon` |
+| `requiredPermissions` | `string[]` | — | 谁可以打开这个应用 |
+| `homePageId` | `string` | — | 用作着陆页的导航项 `id` |
+| `mobileNavigation` | `object` | — | 移动端专属导航 |
+
+## 导航项
+
+导航树支持八种项目类型。常用的五种是 `object`、`dashboard`、`page`、`url` 和 `group`;规范还定义了 `report`、`action` 和 `component` 三种。
+
+| `type` | 落到哪里 | 关键配置 |
+|:--|:--|:--|
+| `object` | 对象的列表视图 | `objectName`,外加可选的定位配置(见下) |
+| `dashboard` | 一个仪表盘 | `dashboardName` |
+| `page` | 一个自定义页面 | `pageName`、可选 `params` |
+| `url` | 一个外部 URL | `url`、`target: '_blank'` |
+| `group` | 可折叠分组 | `children`、`expanded` |
+
+每个导航项**必须**声明唯一的 `id`(小写 `snake_case`)—— `homePageId` 和 `mobileNavigation.bottomNavItems` 引用的就是它。公共属性:`label`、`icon`、`order`、`badge`,再加下面的门控三件套。
+
+### 定位 `object` 入口
+
+三个可选字段可以细化 `object` 入口落到哪里,优先级为 `recordId` → `filters` → `viewName`:
+
+- `viewName` —— 把入口锚定到某个具名列表视图。
+- `recordId` —— 深链到单条记录("我的资料");支持 `{current_user_id}` / `{current_org_id}` 模板变量。
+- `filters` —— 一次性的参数化切片:入口落到裸数据界面上,每个条件是一个可移除的 URL 筛选标签。适合"分派给我"这类链接,不必专门写视图。这不是安全特性 —— 显示什么仍由行级权限决定。
+
+```ts
+{ id: 'nav_my_open', type: 'object', label: 'My Open Deals', objectName: 'opportunity',
+ filters: { owner_id: '{current_user_id}', status: 'open' }, icon: 'user-check' }
+```
+
+### 移动端导航
+
+```ts
+mobileNavigation: {
+ mode: 'bottom_nav', // 'drawer'(默认)| 'bottom_nav' | 'hamburger'
+ bottomNavItems: ['nav_home', 'nav_accounts', 'nav_contacts'], // 导航项 id,最多 5 个
+}
+```
+
+## 按受众门控应用
+
+同一份数据服务于两类截然不同的受众:设计 Schema 的**构建者**,和只录入、查看数据的**最终用户**。默认就把这两类界面分开 —— 别指望每位管理员手动把东西藏起来。
+
+| 受众 | 界面 | 门控方式 |
+|:--|:--|:--|
+| 最终用户(消费者) | 精心组织的应用 → `page` / `view` 入口 | `App.requiredPermissions`、导航项门控 |
+| 构建者 / 管理员 | Setup / Studio、原始对象表格 | 能力:`setup.access`、`studio.access`、`manage_metadata` |
+
+内置权限集已经编码了这种分割:`member_default` 和 `viewer_readonly` **不**携带 `studio.access` / `manage_metadata`,因此构建者界面对他们不可见;`admin_full_access` 和 `organization_admin` 则携带。
+
+每个导航项支持三种相互独立的门控:
+
+| 门控 | 类型 | 隐藏该项,除非…… |
+|:--|:--|:--|
+| `requiredPermissions` | `string[]` | 用户持有该 RBAC 能力(如 `manage_metadata`) |
+| `visible` | CEL 表达式 | 谓词求值为 true(如 `'org_admin' in current_user.positions`) |
+| `requiresObject` / `requiresService` | `string` | 具名对象 / 内核服务已安装 |
+
+```ts
+navigation: [
+ { id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'showcase_contact' },
+ // 仅构建者可见的入口 —— 消费者永远不会渲染它:
+ { id: 'nav_designer', type: 'component', label: 'Object Designer',
+ componentRef: 'metadata:resource', params: { type: 'object' },
+ requiredPermissions: ['manage_metadata'] },
+]
+```
+
+> **隐藏,而不是禁用。**禁用但可见的构建者入口仍是噪音。被门控的导航项对缺少该能力的用户*根本不渲染* —— 不留下让最终用户困惑的灰色摆设。
+
+还有两个习惯能让消费者界面保持干净:
+
+- **给最终用户一个页面,而不是原始表格。**`page` 入口让你精确策划暴露的内容 —— 精选的列、固定的可视化、只有你启用的筛选和操作。`object` 入口给出的则是宽松的表格:可切换视图、个人视图、完整工具栏。
+- **也要策划数据界面本身。**优先用[精心组织的视图](/docs/build/interface/views),而不是放开原始对象读取,把用户扔到一个 40 列的表格上。
+
+操作门控是双面的:UI 隐藏或禁用按钮,**同时**服务端拒绝调用 —— 不存在"只在 UI 门控、服务端敞开"的坑。见[操作](/docs/build/interface/actions)。
+
+## Setup 应用 —— 一个内置示例
+
+平台自身的管理 UI —— **Setup(管理后台)应用** —— 本身就是用同一套应用元数据协议渲染的:导航、页面与门控都声明为数据,由渲染你的应用的同一个渲染器绘制。它的门控方式正是你的构建者界面应有的方式:藏在消费者权限集不携带的 `setup.access` 能力后面。如果你想要"应用即元数据"可以规模化的证据 —— 你已经在用了。
+
+## 反模式
+
+- **把每个最终用户都当成构建者级协作者**,再一个个隐藏选项卡。应该让消费者 / 构建者分割成为默认。
+- **禁用而不是隐藏构建者入口。**可见但失效的摆设照样让人困惑。
+- **放开原始对象读取**,而其实一个精心组织的页面就能恰好暴露最终用户需要的内容。
+
+## 下一步
+
+| 页面 | 原因 |
+|:--|:--|
+| [视图](/docs/build/interface/views) | `object` 导航入口落到的地方 |
+| [页面](/docs/build/interface/pages) | 面向最终用户的精心组织的 `page` 入口 |
+| [仪表盘](/docs/build/interface/dashboards) | `dashboard` 入口渲染的内容 |
+| [操作](/docs/build/interface/actions) | 按钮及其双面门控 |
+| [权限](/docs/configure/permissions) | `requiredPermissions` 背后的权限集 |
+| [界面总览](/docs/build/interface) | 各部分如何组合 |
diff --git a/content/docs/build/interface/dashboards.zh-Hans.mdx b/content/docs/build/interface/dashboards.zh-Hans.mdx
new file mode 100644
index 0000000..a72fa62
--- /dev/null
+++ b/content/docs/build/interface/dashboards.zh-Hans.mdx
@@ -0,0 +1,190 @@
+---
+title: 仪表盘
+description: 由绑定具名数据集的图表组件构成的分析页面 —— 带全局筛选、自动刷新,以及到底层记录的下钻。
+---
+
+# 仪表盘
+
+**仪表盘是一张组件网格;每个组件都绑定到一个数据集。**数据集是语义层:它拥有基础对象、连接、维度和经过认证的度量。组件按名称选用它们,因此"revenue"在每个用到它的仪表盘和报表上含义都相同。
+
+```ts
+const salesDashboard = {
+ name: 'sales_overview',
+ label: 'Sales Overview',
+ description: 'Key sales metrics and pipeline analysis',
+ refreshInterval: 300, // 每 5 分钟自动刷新
+
+ dateRange: {
+ field: 'close_date',
+ defaultRange: 'this_quarter',
+ allowCustomRange: true,
+ },
+
+ widgets: [
+ { id: 'total_revenue', title: 'Total Revenue', type: 'metric',
+ dataset: 'sales', values: ['revenue'],
+ layout: { x: 0, y: 0, w: 3, h: 2 } },
+ { id: 'revenue_by_region', title: 'Revenue by Region', type: 'bar',
+ dataset: 'sales', dimensions: ['region'], values: ['revenue'],
+ layout: { x: 3, y: 0, w: 6, h: 4 } },
+ { id: 'deals_by_month', title: 'Deals by Month', type: 'pie',
+ dataset: 'sales', dimensions: ['close_month'], values: ['deal_count'],
+ layout: { x: 9, y: 0, w: 3, h: 4 } },
+ ],
+}
+```
+
+## 仪表盘属性
+
+| 属性 | 类型 | 必填 | 说明 |
+|:--|:--|:--|:--|
+| `name` | `string` | 是 | 机器名(`snake_case`) |
+| `label` | `string` | 是 | 显示标签 |
+| `description` | `string` | — | 仪表盘描述 |
+| `widgets` | `DashboardWidget[]` | 是 | 图表与指标组件 |
+| `refreshInterval` | `number` | — | 自动刷新间隔(秒) |
+| `dateRange` | `object` | — | 全局日期范围筛选 |
+| `globalFilters` | `GlobalFilter[]` | — | 交互式筛选控件 |
+
+## 数据集优先
+
+数据集定义一次;每个组件都绑定到它:
+
+```ts
+import { defineDataset } from '@objectstack/spec/ui'
+
+export const salesDataset = defineDataset({
+ name: 'sales',
+ label: 'Sales',
+ object: 'opportunity',
+ include: ['account'],
+ dimensions: [
+ { name: 'region', field: 'account.region', type: 'string' },
+ { name: 'close_month', field: 'close_date', type: 'date', dateGranularity: 'month' },
+ ],
+ measures: [
+ { name: 'revenue', field: 'amount', aggregate: 'sum', certified: true },
+ { name: 'deal_count', aggregate: 'count' },
+ ],
+})
+```
+
+聚合放在数据集的度量上(`aggregate`),而不是组件上:`count`、`sum`、`avg`、`min`、`max`、`count_distinct`、`array_agg`、`string_agg`。组件的 `dimensions` 和 `values` 必须引用其所绑定数据集上声明的名称。
+
+运行时,数据集查询经由分析服务执行,并自动把调用者的行级安全范围应用到基础对象和被连接的对象上 —— 仪表盘绝不会向用户展示其[权限](/docs/configure/permissions)不允许看的记录。
+
+> 旧的内联查询形态(在组件上直接写 `object` + `categoryField` + `valueField` + `aggregate`)已经移除。请定义数据集并把组件绑定上去。
+
+## 组件
+
+| 属性 | 类型 | 必填 | 说明 |
+|:--|:--|:--|:--|
+| `id` | `string` | 是 | 唯一的组件 id(`snake_case`) |
+| `dataset` | `string` | 是 | 要绑定的数据集名 |
+| `values` | `string[]` | 是 | 度量名(至少一个) |
+| `dimensions` | `string[]` | — | 维度名 —— X 轴 / 分组 / 拆分 |
+| `type` | `ChartType` | — | 可视化类型(默认 `metric`) |
+| `title` / `description` | `string` | — | 显示标题与副标题 |
+| `filter` | `FilterCondition` | — | 展示层筛选 |
+| `layout` | `object` | — | 网格位置(省略时自动排布) |
+| `colorVariant` | `enum` | — | KPI / 卡片强调色 |
+| `compareTo` | `enum \| object` | — | 同比 / 环比对比窗口 |
+| `filterBindings` | `object` | — | 组件级全局筛选映射(见下) |
+
+### 图表类型
+
+| 类型 | 最适合 |
+|:--|:--|
+| `metric`*(默认)* | 单数字 KPI —— 营收、数量、百分比 |
+| `bar` / `horizontal-bar` / `column` | 类别对比 |
+| `line` | 随时间的趋势 |
+| `pie` / `donut` | 分布 |
+| `area` | 随时间的体量 |
+| `scatter` | 相关性 |
+| `radar` | 多维对比 |
+| `funnel` | 转化阶段 |
+| `gauge` / `solid-gauge` / `bullet` / `kpi` | 目标进度 |
+| `treemap` / `sankey` | 层级占比 / 流向 |
+| `table` | 明细记录 —— **可下钻** |
+| `pivot` | 交叉汇总 —— **可下钻** |
+
+### 布局
+
+组件排布在 12 列网格上:
+
+```ts
+layout: {
+ x: 0, // 列位置(0-11)
+ y: 0, // 行位置
+ w: 6, // 宽度,按列计(1-12)
+ h: 4, // 高度,按行计
+}
+```
+
+## 下钻
+
+`table` 和 `pivot` 组件支持**下钻**:点击一行聚合数据或一个单元格,会打开一个侧边抽屉,列出该分组背后的*底层记录*。数据集保留了原始分组键,所以抽屉的筛选精确匹配那些记录 —— 不做标签到 id 的猜测。点击抽屉里的任意一行即可打开该记录的详情:完整的**分组 → 记录列表 → 单条记录**链路。
+
+抽屉还提供一个 **"Open in list →"**(在列表中打开)逃生口,把速览升级为该对象的完整列表页(排序、批量选择、导出、可分享 URL),并沿用同一个下钻筛选。
+
+下钻是自动的 —— 无需按组件配置 —— 只要数据集暴露了基础对象,且组件按至少一个维度分组。`metric` 与图表类组件只渲染聚合值;要展示明细,请改用 `table` 或 `pivot` 组件。
+
+## 全局日期范围与筛选
+
+全局时间筛选作用于所有组件:
+
+```ts
+dateRange: { field: 'created_at', defaultRange: 'this_month', allowCustomRange: true }
+```
+
+预设范围:`today`、`yesterday`、`this_week`、`last_week`、`this_month`、`last_month`、`this_quarter`、`last_quarter`、`this_year`、`last_year`、`last_7_days`、`last_30_days`、`last_90_days`、`custom`。
+
+交互式全局筛选的用法相同:
+
+```ts
+globalFilters: [
+ { name: 'region', field: 'region', label: 'Region', type: 'select' },
+ { field: 'owner', label: 'Sales Rep', type: 'lookup' },
+]
+```
+
+每个筛选的 `name`(默认取 `field`)是它的稳定身份 —— 组件在 `filterBindings` 里引用的键,也是组件表达式中可通过 `page.` 读取的仪表盘级变量。名称 `dateRange` 保留给内置日期范围。
+
+### 组件级筛选绑定
+
+默认情况下,筛选按其自身的 `field` 作用于每个组件。当某个组件用不同的字段存储同一概念 —— 或应忽略某个筛选 —— 时,声明 `filterBindings`:
+
+```ts
+widgets: [
+ // 默认绑定:dateRange → created_at,region → region。
+ { id: 'invoices_by_status', /* … */ },
+ // 这个组件的字段不同 —— 逐个显式映射筛选。
+ { id: 'accounts_signed',
+ filterBindings: { dateRange: 'signed_at', region: 'sales_region' }, /* … */ },
+ // 用 `false` 退出某个筛选。
+ { id: 'total_invoices', filterBindings: { region: false }, /* … */ },
+]
+```
+
+> 优先级:显式的 `filterBindings` 条目(字符串覆盖或 `false` 退出)→ 筛选的旧式 `targetWidgets` 白名单 → 筛选自身的 `field`。
+
+## 让仪表盘出现在界面上
+
+给[应用](/docs/build/interface/apps)添加一个 `dashboard` 导航入口:
+
+```ts
+{ id: 'nav_analytics', type: 'dashboard', label: 'Analytics',
+ dashboardName: 'sales_overview', icon: 'bar-chart' }
+```
+
+或者描述给 [AI Builder](/docs/build/ai-builder) —— *"一个销售总览仪表盘,含按区域的营收和月度成交趋势"* —— 然后在批准前审阅生成的数据集 + 仪表盘元数据。
+
+## 下一步
+
+| 页面 | 原因 |
+|:--|:--|
+| [应用](/docs/build/interface/apps) | 把仪表盘放进导航 |
+| [视图](/docs/build/interface/views) | 基于同一数据集的内联 `chart` 列表视图 |
+| [页面](/docs/build/interface/pages) | 当组件网格不够用时的自由布局 |
+| [数据模型](/docs/build/data) | 数据集聚合的对象 |
+| [权限](/docs/configure/permissions) | 仪表盘继承的行级安全 |
diff --git a/content/docs/build/interface/forms.zh-Hans.mdx b/content/docs/build/interface/forms.zh-Hans.mdx
new file mode 100644
index 0000000..f36202b
--- /dev/null
+++ b/content/docs/build/interface/forms.zh-Hans.mdx
@@ -0,0 +1,161 @@
+---
+title: 表单
+description: 从一份扁平字段集派生创建与编辑表单,无漂移地把字段分组成区块,并控制提交后的去向。
+---
+
+# 表单
+
+**表单是对象字段的一个投影 —— 不是第二份字段列表。**字段在对象上只声明一次;表单只决定*哪些字段、放在哪里*。数据语义(类型、验证、默认值、字段级安全)从不放在表单上,所以表单不可能漂移到对数据"说谎"。
+
+表单视图的*类型*(`simple`、`tabbed`、`wizard`、`modal`……)与公开表单的 REST 契约在[视图](/docs/build/interface/views)中讲。本页讲布局问题:创建与编辑、分组、排序。
+
+## 创建表单 ≠ 编辑表单
+
+新建记录的表单应该只问几个要点;编辑表单则分区块展示完整记录。**别写两份表单。**创建表单的字段子集可以从每个字段上已有的意图*派生*出来:
+
+| 字段信号 | 对创建表单的影响 |
+|:--|:--|
+| `required: true` | **必须**出现在创建表单上 |
+| `readonly` / 公式 / 汇总 / 自动编号 / 系统写入 | **绝不**出现在创建表单上 —— 你设置不了它 |
+| 有 `defaultValue` | **可省略** —— 它会自动填充 |
+| `hidden` | 默认处处不显示 |
+| `group` | 字段属于哪个区块 |
+| *声明顺序* | **就是**默认显示顺序 —— 没有 `field.order` |
+
+于是合理的创建表单 —— *可编辑、必填或核心的字段,按声明顺序* —— 零编写就从对象里长出来了。省略即正确:什么都不多写,你依然得到一份完整、正确的表单。完整的编辑表单同样是派生的,把每个 `field.group` 实体化为一个区块。
+
+### 只在布局或流程真的不同时,才手工塑造创建表单
+
+逃生舱是把一个具名表单视图绑定到创建入口。把它写成一份**稀疏覆盖** —— 基本上只是一串字段名:
+
+```ts
+import { defineView } from '@objectstack/spec'
+
+const data = { provider: 'object' as const, object: 'showcase_contact' }
+
+export const ContactViews = defineView({
+ list: {
+ type: 'grid', data,
+ columns: [{ field: 'name' }, { field: 'email' }, { field: 'company' }, { field: 'stage' }],
+ // 把 "+ Add record" 入口绑定到精简的创建表单:
+ addRecord: { enabled: true, mode: 'form', formView: 'create' },
+ },
+
+ // 完整编辑表单 —— 按 field.group 分组;裸字符串继承字段定义。
+ form: {
+ type: 'simple', data,
+ sections: [
+ { name: 'contact', label: 'Contact', columns: 2, fields: ['name', 'email', 'phone'] },
+ { name: 'work', label: 'Work', columns: 2, fields: ['company', 'title'] },
+ { name: 'status', label: 'Status', columns: 2, fields: ['stage', 'lead_score'] },
+ ],
+ },
+
+ formViews: {
+ // 稀疏的创建覆盖:只留核心字段,单个区块。
+ create: {
+ type: 'simple', data, title: 'New contact',
+ sections: [
+ { label: 'Who is this?', columns: 1, fields: ['name', 'email', 'phone', 'company'] },
+ ],
+ },
+ },
+})
+```
+
+绑定方式是 `addRecord.mode: 'form'` + `addRecord.formView: 'create'`。没有 `formViews.create` → 创建入口派生默认表单。有 → 创建时它生效。
+
+| 你的真实需求 | 用 |
+|:--|:--|
+| 创建时少问几个字段(必填 + 少数核心) | **纯派生** —— 不要手写 |
+| 创建时分组不同,但仍只是更小的子集 | 通常仍然派生 |
+| 创建是向导 / 多步、有创建专属文案、条件展开 | **手写 `formViews.create`** |
+
+> 经验法则:*字段子集不同* → 派生。*布局或流程不同* → 覆盖。为了删几个字段就写一份完整的创建表单,等于直接走进"双份产物漂移"的陷阱:新增一个必填字段、忘了改创建表单,创建时就得到运行时的"缺少必填字段"错误。
+
+因为创建表单的字段是编辑表单字段的子集,*顺序和分组也相同*,"快速创建 4 个字段 → 保存 → 落到完整记录页"在视觉上是连续的。派生免费保住了这一点。
+
+## 字段分组与顺序
+
+模型是一份扁平字段集。表格显示扁平的列。表单需要区块。这不矛盾 —— 是同一份扁平集合透过不同的镜头:
+
+| 镜头 | 字段形态 | 原因 |
+|:--|:--|:--|
+| 对象定义 | 扁平 | 模型描述*存在哪些数据* |
+| 表格 / Grid | 扁平的列 | 表格是记录 × 字段的矩阵 |
+| 表单 / 记录页 | 分组区块 | 人阅读单条记录时需要分块 |
+
+分组概念有**两个**。保持区分:
+
+**1. 语义分组 —— `field.group`(在对象上)。**字段的逻辑归属。它随模型走,并为自动生成表单的*默认*分区提供种子:
+
+```ts
+fields: {
+ name: Field.text({ label: 'Full name', group: 'contact' }),
+ email: Field.email({ label: 'Email', group: 'contact' }),
+ stage: Field.select({ label: 'Stage', group: 'status', options: [/* … */] }),
+}
+```
+
+**2. 布局分组 —— 表单 `sections`(在视图上)。**某个具体表单的显式编排:哪些字段、哪个区块、几个 `columns`、可否折叠。它以 `field.group` 为默认继承来源,需要时按表单覆盖。
+
+```ts
+form: {
+ type: 'simple',
+ sections: [
+ { name: 'contact', label: 'Contact', columns: 2, fields: ['name', 'email', 'phone'] },
+ { name: 'status', label: 'Status', columns: 2, fields: ['stage'] },
+ ],
+}
+```
+
+**顺序就是你书写的顺序。**没有 `field.order` —— 对象上字段的声明顺序就是处处的默认显示顺序,而 `sections`(以及其中的字段)是有序列表。
+
+> **"group" 陷阱。**表格视图的 `groupByField` 按字段的*值*给**记录(行)**分组 —— 所有 `stage = qualified` 的行归到一起。表单区块在视觉上给**字段(列)**分组。同一个词,两条互不相关的轴。表格分组永远不会给你的表单分区。
+
+## 提交之后会发生什么
+
+给表单视图加 `submitBehavior` 来控制提交后的体验:
+
+```ts
+submitBehavior: {
+ kind: 'thank-you',
+ title: 'Thanks!',
+ message: 'A specialist will reach out within 24 hours.',
+}
+```
+
+| `kind` | 行为 |
+|:--|:--|
+| `thank-you`*(默认)* | 用一块确认面板替换表单(`title`、`message`) |
+| `redirect` | 在 `delayMs`(默认 0)之后跳转到 `url` —— 适合营销页 |
+| `continue` | 重置表单以填写下一份 —— 自助终端、批量录入 |
+| `next-record` | 前进到队列中的下一条记录(内部模式) |
+
+公开表单和内部表单都支持 `?prefill_=` URL 参数 —— 从邮件链接或活动页预填表单。预填是体验捷径,不是权限绕过:值仍要经过验证,公开表单还要过服务端字段白名单。
+
+```
+/console/f/contact-us?prefill_company=Acme&prefill_email=ada@example.com
+/console/forms/quick_create?prefill_lead_source=event_booth_2026
+```
+
+## 记录详情由角色驱动,而非表单绑定
+
+没有哪个对象级键能把表单视图钉到记录详情屏上。详情渲染由对象的跨界面**语义角色**派生:`nameField`(显示名)、`highlightFields`(最重要字段组成的条带)、`stageField`(生命周期进度条)、以及 `fieldGroups` + `Field.group`(表单、弹窗与详情页共享的分区)。当记录页需要的定制布局超出这些角色所能派生的范围时,给对象指定一个自定义[页面](/docs/build/interface/pages)。
+
+## 反模式
+
+- **两份完整手写的字段列表**(`contact_create_form` + `contact_edit_form`)。必然漂移。
+- **在表单上复述字段类型 / 验证 / 选项。**数据语义只属于对象。
+- **在每个表单里重新敲一遍分组。**`field.group` 声明一次;只在真正分歧时覆盖。
+- **为了迁就表单给数据模型加结构性嵌套。**模型保持扁平;分组交给表单。
+
+## 下一步
+
+| 页面 | 原因 |
+|:--|:--|
+| [视图](/docs/build/interface/views) | 表单视图类型、公开表单分享、完整的视图 Schema |
+| [数据模型](/docs/build/data) | 字段 —— 以及它们的意图 —— 声明的地方 |
+| [操作](/docs/build/interface/actions) | 启动表单视图的 `type: 'form'` 操作 |
+| [页面](/docs/build/interface/pages) | 超越派生布局的自定义记录页 |
+| [权限](/docs/configure/permissions) | 表单遵守的字段级安全 |
diff --git a/content/docs/build/interface/index.zh-Hans.mdx b/content/docs/build/interface/index.zh-Hans.mdx
new file mode 100644
index 0000000..f1dd9ee
--- /dev/null
+++ b/content/docs/build/interface/index.zh-Hans.mdx
@@ -0,0 +1,45 @@
+---
+title: 界面
+description: 应用、视图、表单、仪表盘、页面与操作 —— 每个面向用户的界面都声明为元数据,由 Console 渲染。
+---
+
+# 界面
+
+**用户看到的一切都是元数据。**你把界面声明为数据 —— 与对象同一套生命周期:可以版本化、打进包里发布、让 AI Builder 生成,任何符合协议的渲染器都能把它画出来。
+
+各个部分自上而下组合:
+
+1. **应用**是外壳 —— 导航、品牌,以及受众门槛。
+2. 它的**导航**指向对象、仪表盘、页面和 URL。
+3. 对象入口落到一个**视图**上 —— Grid(表格)、Kanban(看板)、Calendar(日历)等等。
+4. 打开或创建记录时渲染**表单** —— 区块由字段元数据派生,可按模式覆盖。
+5. **仪表盘**把同样的数据聚合成绑定到具名数据集的图表组件。
+6. **页面**完全跳出对象框架 —— 由组件和区域组合的自由布局。
+7. **操作**给上述一切装上按钮 —— 一次声明,可从 Console、REST、流程和 AI Agents 调用。
+
+## 核心概念
+
+| 概念 | 声明什么 | 页面 |
+|:--|:--|:--|
+| 应用 | 导航外壳、品牌、受众门控 | [应用](/docs/build/interface/apps) |
+| 视图 | 记录如何渲染:Grid、Kanban、Calendar、Gantt…… | [视图](/docs/build/interface/views) |
+| 表单 | 创建 / 编辑布局、区块、字段分组 | [表单](/docs/build/interface/forms) |
+| 仪表盘 | 图表组件、全局筛选、下钻 | [仪表盘](/docs/build/interface/dashboards) |
+| 页面 | 自由组件布局;随包发布的文档页 | [页面](/docs/build/interface/pages) |
+| 操作 | 处处以按钮形式呈现的具名操作 | [操作](/docs/build/interface/actions) |
+
+因为 UI 是数据,它遵守与其所展示的记录相同的[权限](/docs/configure/permissions);而 [AI Builder](/docs/build/ai-builder) 可以通过同一个类型化接口生成或修改其中任何部分 —— 描述你想要的界面,审阅 diff,批准。
+
+> 从数据开始。视图和表单会从你的[对象定义](/docs/build/data)派生出合理的默认值 —— 只有当默认不是你想要的时,才需要写界面元数据。
+
+## 下一步
+
+| 页面 | 原因 |
+|:--|:--|
+| [应用](/docs/build/interface/apps) | 把对象包进可导航的外壳 |
+| [视图](/docs/build/interface/views) | 选择记录的渲染方式 |
+| [表单](/docs/build/interface/forms) | 塑造创建与编辑体验 |
+| [仪表盘](/docs/build/interface/dashboards) | 添加分析组件 |
+| [页面](/docs/build/interface/pages) | 组合自由布局 |
+| [操作](/docs/build/interface/actions) | 把操作放到按钮后面 |
+| [数据模型](/docs/build/data) | 每个界面读取的对象 |
diff --git a/content/docs/build/interface/pages.zh-Hans.mdx b/content/docs/build/interface/pages.zh-Hans.mdx
new file mode 100644
index 0000000..af51ab1
--- /dev/null
+++ b/content/docs/build/interface/pages.zh-Hans.mdx
@@ -0,0 +1,156 @@
+---
+title: 页面
+description: 由区域、组件与本地状态组合的自由布局 —— 外加随包发布的 Markdown 文档页。
+---
+
+# 页面
+
+**页面是一个自由容器。**不同于绑定到单个对象的[视图](/docs/build/interface/views),页面组合多个组件、嵌入视图并管理本地状态 —— 你的主屏、自定义记录布局和工具面板都靠它。
+
+```ts
+const homePage = {
+ name: 'sales_home',
+ label: 'Sales Home',
+ type: 'home',
+ regions: [
+ { name: 'header', width: 'full', components: [
+ { type: 'metric_card', id: 'total_revenue', label: 'Total Revenue',
+ properties: { object: 'opportunity', field: 'amount', aggregate: 'sum', format: 'currency' } },
+ ]},
+ { name: 'main', width: 'large', components: [
+ { type: 'list_view', id: 'recent_deals', label: 'Recent Deals',
+ properties: { object: 'opportunity', view: 'recent_open', limit: 10 } },
+ ]},
+ ],
+}
+```
+
+## 页面属性
+
+| 属性 | 类型 | 必填 | 说明 |
+|:--|:--|:--|:--|
+| `name` | `string` | 是 | 机器名(`snake_case`) |
+| `label` | `string` | 是 | 显示标签 |
+| `type` | `enum` | — | 页面类型(默认 `'record'`,见下) |
+| `object` | `string` | — | 关联对象(`record` 类型用) |
+| `template` | `string` | — | 布局模板名(默认 `'default'`) |
+| `regions` | `PageRegion[]` | — | 承载组件的布局区域 |
+| `variables` | `PageVariable[]` | — | 本地状态变量 |
+| `isDefault` | `boolean` | — | 是否为该类型的默认页面 |
+| `assignedProfiles` | `string[]` | — | 可访问此页面的简档 |
+
+### 页面类型
+
+| 类型 | 用于 |
+|:--|:--|
+| `record` | 绑定到某个对象记录的自定义详情页 |
+| `home` | 应用着陆 / 入口 |
+| `app` | 通用应用布局 |
+| `utility` | 工具、设置、向导 |
+| `list` | 数据驱动的界面 |
+
+只有这五种类型有效 —— 早期路线图上那些从未交付渲染器的类型已从 Schema 中移除。
+
+## 区域
+
+区域是布局分区;每个区域承载组件:
+
+```ts
+regions: [
+ { name: 'sidebar', width: 'small', components: [/* … */] },
+ { name: 'content', width: 'large', components: [/* … */] },
+]
+```
+
+`width` 接受 `'small'`、`'medium'`、`'large'` 或 `'full'`。
+
+## 组件
+
+组件是区域内部的积木:
+
+```ts
+{
+ type: 'chart',
+ id: 'revenue_chart',
+ label: 'Revenue Trend',
+ properties: { chartType: 'line', object: 'opportunity',
+ categoryField: 'close_date', valueField: 'amount' },
+ events: { onClick: "navigate_to('opportunity_detail', { id: $event.id })" },
+ visibility: "os.user.profile == 'sales_manager'",
+}
+```
+
+| 属性 | 类型 | 说明 |
+|:--|:--|:--|
+| `type` | `string` | 标准组件类型,或自定义字符串 |
+| `id` | `string` | 唯一的组件实例 id |
+| `properties` | `object` | 组件专属配置 |
+| `events` | `object` | 事件处理器(操作表达式) |
+| `visibility` | `string` | CEL 可见性谓词 |
+| `style` / `className` | — | 临时 CSS |
+| `responsiveStyles` | `object` | **首选**样式通道:桌面优先、按断点的样式映射(`large` 为基础,再叠加 `medium` / `small` / `xsmall` 覆盖),编译为作用域 CSS —— 优先用 `var(--space-8)` 这样的设计令牌 |
+
+标准的、带命名空间的组件类型:
+
+| 命名空间 | 类型 |
+|:--|:--|
+| 结构 | `page:header`、`page:footer`、`page:sidebar`、`page:tabs`、`page:accordion`、`page:card`、`page:section` |
+| 记录上下文 | `record:details`、`record:highlights`、`record:related_list`、`record:activity`、`record:chatter`、`record:path`、`record:alert`、`record:quick_actions`、`record:reference_rail`、`record:history` |
+| 导航 | `app:launcher`、`nav:menu`、`nav:breadcrumb` |
+| 工具 | `global:search`、`global:notifications`、`user:profile` |
+| AI | `ai:chat_window`、`ai:suggestion` |
+| 元素 | `element:text`、`element:number`、`element:image`、`element:divider`、`element:button`、`element:filter`、`element:form`、`element:record_picker`、`element:text_input` |
+
+组件还可以携带 `dataSource`(多对象页面中按元素绑定对象)、`responsive` 与 `aria` 配置。项目专属的组件可以使用自定义字符串类型。
+
+## 变量
+
+页面持有跨组件共享的本地状态:
+
+```ts
+variables: [
+ { name: 'selected_tab', type: 'string', defaultValue: 'overview' },
+ { name: 'date_range', type: 'object', defaultValue: { start: null, end: null } },
+]
+```
+
+`type` 接受 `'string'`(默认)、`'number'`、`'boolean'`、`'object'`、`'array'` 或 `'record_id'`。
+
+> `record` 类型的页面是定制记录布局的受支持路径 —— 当由角色派生的详情页(见[表单](/docs/build/interface/forms))不够用时就用它。把 `record:highlights` 组合进全宽头部,把 `record:details` 和 `record:activity` 放进侧栏,把 `record:related_list` 组件放进主区域。
+
+通过 `page` 导航入口把页面挂到[应用](/docs/build/interface/apps)上,或者用 `homePageId` 把它设为应用的着陆页。
+
+## 文档页
+
+**doc** 是一页包文档:放在扁平的 `src/docs/` 目录里的纯 Markdown 文件,编译进包产物,在 Console 中渲染于 `/docs/`。文档还会在 AI 助手回答关于你的包的问题时充当依据。
+
+```
+src/docs/
+ crm_index.md → 文档名 "crm_index"
+ crm_user_guide.md → 文档名 "crm_user_guide"
+```
+
+文件名主干就是文档 `name` —— 没有目录分级,没有排序文件。扁平布局让交叉引用保持稳定:链接按 basename 解析,从不按路径。
+
+| 规则 | 细节 |
+|:--|:--|
+| 命名 | `snake_case`,且构建 lint 要求**命名空间前缀**(`crm_user_guide`,而非 `user_guide`) |
+| Frontmatter | 可选;读取 `title` 和 `description`。标题解析顺序:frontmatter → 第一个 `#` 标题 → 文档名 |
+| 交叉引用 | 普通相对链接(`[overview](./crm_index.md)`)—— 在 Console 里重写为 `/docs/`,在 GitHub 上原生可用。**同包内的坏链会导致构建失败** |
+| Markdown | CommonMark + GFM:表格、带高亮的围栏代码、标题锚点、GitHub alerts(`> [!TIP]`) |
+| 构建时拒绝 | **MDX / 内嵌组件**(文档是数据,不是代码 —— 这是信任边界)以及**图片引用**(尚无资产服务;快速失败胜过碎图) |
+
+文档解析按包隔离:两个已安装的包可以各自发布同名裸文档并共存 —— 谁也不会覆盖谁。
+
+> 需要动态内容 —— 实时流程图、记录表格 —— 时别试图内嵌组件。用 URL 链接到元数据;平台渲染实时视图,文档只负责指向它。
+
+## 下一步
+
+| 页面 | 原因 |
+|:--|:--|
+| [应用](/docs/build/interface/apps) | 把页面放进导航、设置 `homePageId` |
+| [视图](/docs/build/interface/views) | 页面所嵌入的对象界面 |
+| [表单](/docs/build/interface/forms) | 角色派生的记录布局 vs 自定义记录页 |
+| [仪表盘](/docs/build/interface/dashboards) | 以分析为中心的布局,而非自由布局 |
+| [操作](/docs/build/interface/actions) | `element:button` 的目标与 `modal` 类型操作 |
+| [AI Builder](/docs/build/ai-builder) | 用对话生成页面元数据 |
diff --git a/content/docs/build/interface/views.zh-Hans.mdx b/content/docs/build/interface/views.zh-Hans.mdx
index 64a0327..d46e085 100644
--- a/content/docs/build/interface/views.zh-Hans.mdx
+++ b/content/docs/build/interface/views.zh-Hans.mdx
@@ -1,9 +1,9 @@
---
-title: Views
+title: 视图
description: List、Form、Kanban、Calendar、Gantt 等等 —— Console 中每个对象表面是如何声明的。
---
-# Views
+# 视图
**view** 是用户在 Console 中查看和编辑记录的方式。视图是声明式元数据 —— 生命周期与对象相同:声明一次,随你的 package 发布,在任何地方渲染。
diff --git a/content/docs/build/marketplace.zh-Hans.mdx b/content/docs/build/marketplace.zh-Hans.mdx
index 56d8a6b..0b3670f 100644
--- a/content/docs/build/marketplace.zh-Hans.mdx
+++ b/content/docs/build/marketplace.zh-Hans.mdx
@@ -9,7 +9,30 @@ ObjectOS marketplace 让你能够将预构建的应用安装到运行中的运
## 工作原理
-每个 ObjectOS 运行时默认都启用了 `MarketplaceProxy` 和 `MarketplaceInstallLocal` 插件。当你打开 Console(`/_console/`)时,marketplace 标签页会查询已配置的应用目录,并展示可安装的应用。
+marketplace 客户端是开源的 `@objectstack/cloud-connection` 包。云托管环境和自托管的 ObjectOS 镜像开箱即已接好;如果是你自己组装的技术栈,用几行配置即可启用:
+
+```ts
+import {
+ MarketplaceProxyPlugin,
+ MarketplaceInstallLocalPlugin,
+ RuntimeConfigPlugin,
+ resolveCloudUrl,
+} from '@objectstack/cloud-connection';
+
+const catalogUrl = resolveCloudUrl(); // OS_CLOUD_URL; 'off' disables
+
+plugins: [
+ ...(catalogUrl ? [
+ new MarketplaceProxyPlugin({ controlPlaneUrl: catalogUrl }),
+ new MarketplaceInstallLocalPlugin({ controlPlaneUrl: catalogUrl }),
+ ] : []),
+ new RuntimeConfigPlugin({ controlPlaneUrl: '', singleEnvironment: true, installLocal: true }),
+]
+```
+
+浏览 / 安装的*机制*是开放的;目录*服务*(组织目录、审核、付费分发)由 `OS_CLOUD_URL` 所指向的控制平面提供。应用一经安装就存在于你运行时自己的内核中 —— 运行时的任何部分都不依赖目录保持可达。
+
+当你打开 Console(`/_console/`)时,marketplace 标签页会查询已配置的应用目录,并展示可安装的应用。
```text
You ─→ Console ─→ Marketplace tab ─→ pick app ─→ Install
@@ -82,6 +105,22 @@ os package publish # publish to a catalog
每个已发布的应用都是不可变的。更新会产生一个新版本。运行时会按应用跟踪已安装的版本以及可用的更新。当某个应用在用户所跟踪的目录中发布了新版本时,用户会在 Console 中看到一个 "Update available" 徽标。
+## 通过 CLI 安装
+
+Console 是主要的安装入口,但同一个 install-local 端点也可以脚本化,用于 CI 和隔离网络环境下的运维:
+
+```bash
+# 目录模式 —— 运行时从其配置的目录解析包
+os package install com.acme.crm --runtime http://localhost:3000 \
+ --email admin@example.com --password …
+
+# 隔离网络模式 —— 内联发送编译好的制品,不经过目录往返
+os package install ./dist/objectstack.json --runtime http://localhost:3000 \
+ --email admin@example.com --password …
+```
+
+凭证是**目标运行时**上的账户(不是你的云端登录)—— 与 Console 安装使用同一套授权。
+
## 权限
安装应用需要 `manage_marketplace` 系统权限——默认情况下只有 **Setup Administrator** 权限集的成员拥有该权限。普通用户看到的 marketplace 是只读的。
diff --git a/content/docs/configure/index.zh-Hans.mdx b/content/docs/configure/index.zh-Hans.mdx
new file mode 100644
index 0000000..d5f44e7
--- /dev/null
+++ b/content/docs/configure/index.zh-Hans.mdx
@@ -0,0 +1,59 @@
+---
+title: 管理
+description: 系统管理员在哪里管理用户、访问、设置与集成 —— 以及哪个页面解决哪类任务。
+---
+
+# 管理
+
+本章面向 ObjectOS 部署的**系统管理员**:负责用户入职、授予访问权限、接通登录、邮件、存储与集成,并保持系统健康运转的人。你日常管理的是人和他们的权限;应用、对象和权限集本身则随平台及你安装的应用包一起交付。
+
+> **权限在 Studio 中设计,在 Setup 中分配。**绝大多数管理工作都不会离开 Setup(管理后台)——只有编写权限集或运行解释引擎时才需要进入 Studio。
+
+## Setup 控制台
+
+内置的管理控制台位于 **`/apps/setup`**,需要 `setup.access` 权限。其左侧导航由运行时已加载的能力插件动态提供,因此你看到的菜单精确反映当前部署所运行的内容——未启用的能力不贡献任何菜单项,其分组保持为空。
+
+稳定的导航分组:
+
+| 分组 | 里面有什么 |
+|---|---|
+| **Overview**(概览) | System Overview 系统概览仪表盘 |
+| **People & Organization**(人员与组织) | 用户、业务单元、团队、组织、邀请 |
+| **Access Control**(访问控制) | 岗位、权限集、共享规则、记录共享、API Key |
+| **Approvals**(审批) | 审批流程(加载 approvals 插件时) |
+| **Configuration**(配置) | 全部设置、品牌、认证、邮件、文件存储、AI 与 Embedder、知识库、功能开关 |
+| **Diagnostics**(诊断) | 会话、通知事件、审计日志 |
+| **Integrations**(集成) | Webhooks |
+| **Advanced**(高级) | OAuth 应用、签名密钥(JWKS)、身份关联、用户偏好 |
+
+## 任务地图
+
+| 我需要 …… | 阅读 |
+|---|---|
+| 添加用户、搭建组织树、管理团队 | [用户与组织](/docs/configure/users) |
+| 为某人办理入职、离职或调整访问权限 | [权限分配](/docs/configure/permissions/managing-access) |
+| 理解整套访问模型 | [权限](/docs/configure/permissions) |
+| 配置登录、OAuth、SSO、双因素认证 | [认证](/docs/configure/authentication) |
+| 修改运行时和租户设置 | [系统设置](/docs/configure/system-settings) |
+| 配置事务性邮件发送 | [邮件](/docs/configure/email) |
+| 配置文件存储(S3、本地磁盘) | [存储](/docs/configure/storage) |
+| 连接外部业务数据库 | [数据源](/docs/configure/data-sources) |
+| 使用 REST API 和 API Key | [API 访问](/docs/configure/api-access) |
+| 发送出站 Webhook | [Webhooks](/docs/configure/webhooks) |
+| 配置 AI Provider、Embedder、RAG | [AI 服务](/docs/configure/ai) |
+| 接入 Claude 或其他 MCP 客户端 | [接入 AI 工具(MCP)](/docs/configure/mcp) |
+| 配置制品加载、数据库、缓存 | [运行时配置](/docs/configure/runtime) |
+| 规划备份与灾难恢复 | [备份](/docs/operate/backup) |
+| 安全地升级或回滚 | [升级](/docs/operate/upgrade) |
+| 为生产环境加固 | [生产就绪](/docs/operate/production) |
+| 查看日志、指标和审计记录 | [可观测性](/docs/operate/observability) |
+| 诊断故障部署 | [故障排查](/docs/operate/troubleshooting) |
+
+## 下一步
+
+| 任务 | 页面 |
+|---|---|
+| 你的第一项管理任务:添加人员并搭建结构 | [用户与组织](/docs/configure/users) |
+| 给新员工开通访问权限 | [权限分配](/docs/configure/permissions/managing-access) |
+| 学习分层访问模型 | [权限](/docs/configure/permissions) |
+| 上线前检查清单 | [生产就绪](/docs/operate/production) |
diff --git a/content/docs/configure/mcp.zh-Hans.mdx b/content/docs/configure/mcp.zh-Hans.mdx
new file mode 100644
index 0000000..217f15b
--- /dev/null
+++ b/content/docs/configure/mcp.zh-Hans.mdx
@@ -0,0 +1,129 @@
+---
+title: 接入 AI 工具(MCP)
+description: 把 Claude Code、Claude Desktop 或任意 MCP 客户端指向你的 ObjectOS 应用,让 Agent 在你的权限模型约束下处理你的数据。
+---
+
+# 接入 AI 工具(MCP)
+
+每个 ObjectOS 部署天生就是一个 MCP 服务器。运行时在 **`/api/v1/mcp`** 上提供 [Model Context Protocol](https://modelcontextprotocol.io) 服务——默认开启,无需安装插件,无需配置步骤。你的对象和已暴露的操作在定义的那一刻就成为带类型的工具;剩下唯一要做的就是接入一个客户端并验证它能用。
+
+> 要关闭这个入口,设置 `OS_MCP_SERVER_ENABLED=false`——端点将返回 404,**Setup → Connect an Agent**(接入 Agent)页面也随之消失。
+
+本页讲的是把*外部* AI 工具接入你的应用。服务端 AI 栈——聊天 Provider、Embedder、RAG,以及在代码中显式注册 MCP 服务器插件——参见 [AI 服务](/docs/configure/ai)。
+
+## Claude Code(一条命令)
+
+交互式客户端使用 OAuth——每个部署本身就是一个 OAuth 2.1 授权服务器,因此不存在需要管理员签发再分发的凭据。第一次工具调用会打开浏览器登录,你**以自己的身份**接入:
+
+```bash
+# 本地开发服务器
+claude mcp add --transport http my-app http://localhost:3000/api/v1/mcp
+
+# 已部署的实例
+claude mcp add --transport http my-app https://your-deployment.example.com/api/v1/mcp
+```
+
+无头场景(CI、容器)跳过 OAuth,改为附加 [API Key](#headless-api-keys):
+
+```bash
+claude mcp add --transport http my-app https://your-deployment.example.com/api/v1/mcp \
+ --header "x-api-key: osk_..."
+```
+
+## Claude Desktop 与 claude.ai
+
+**Settings → Connectors → Add custom connector**(设置 → 连接器 → 添加自定义连接器),然后粘贴 MCP URL(`https://your-deployment.example.com/api/v1/mcp`)。首次使用时会走同样的浏览器登录流程。
+
+## 任意 MCP 客户端(`.mcp.json`)
+
+读取 `mcpServers` 映射的客户端以同样方式接入。使用 API Key:
+
+```json
+{
+ "mcpServers": {
+ "objectstack": {
+ "type": "http",
+ "url": "https://your-deployment.example.com/api/v1/mcp",
+ "headers": { "x-api-key": "osk_..." }
+ }
+ }
+}
+```
+
+## 无头场景:API Key
+
+在 **Setup → Connect an Agent**(接入 Agent,那里还提供各客户端可直接复制粘贴的接入片段)中签发 Key,或通过 REST:
+
+```bash
+curl -b cookies.txt -X POST https://your-deployment.example.com/api/v1/keys
+# → { "key": "osk_..." } —— 只显示一次;存入你的 secret manager
+```
+
+每次请求以三种等价形式之一携带它:
+
+| 请求头 | 示例 |
+|---|---|
+| `x-api-key` | `x-api-key: osk_...` |
+| `Authorization: ApiKey` | `Authorization: ApiKey osk_...` |
+| `Authorization: Bearer` | `Authorization: Bearer osk_...`(通过 `osk_` 前缀识别) |
+
+> OAuth 要求 TLS——纯 HTTP 部署(`localhost` 除外)会回退到**仅 API Key** 模式:浏览器登录通道被禁用,而不是被允许以不安全的方式运行。
+
+对于长期集成,把 Key 绑定到带最小权限集的专用服务用户——参见[服务账号与 API Key](/docs/configure/users#service-accounts--api-keys)。
+
+## Agent 能得到什么
+
+十个数据与操作工具,由你的元数据生成:
+
+| 工具 | 用途 |
+|---|---|
+| `list_objects` / `describe_object` | 发现有哪些对象及其字段 |
+| `query_records` / `get_record` | 读取数据(列表查询默认每页上限 50 行) |
+| `aggregate_records` | 分组聚合(当前驱动支持时才注册) |
+| `create_record` / `update_record` / `delete_record` | 写入数据 |
+| `list_actions` / `run_action` | 按名称发现并调用你的业务操作 |
+
+要了解的两条暴露规则:
+
+- **对象自动暴露**——但 `sys_*` 系统对象除外,它们以 fail-closed 方式被拦截。
+- **操作需要作者显式选择加入**:`ai: { exposed: true }` 加上不少于 40 个字符的 `ai.description`,且该操作必须可以在无 UI 的情况下调用(带 body 或已注册 handler 的 `script`,或 `flow`)。
+
+## 权限强制执行
+
+- **每次调用都以调用者身份运行。**MCP 桥接解析的执行上下文与 REST 请求相同,因此对象权限、[记录访问](/docs/configure/permissions/record-access)和[字段级安全](/docs/configure/permissions/field-level-security)对 Agent 的作用与对 UI 中的真人完全一致。结果稀疏或写入被拒,通常意味着治理在*正常工作*,而不是连接坏了。
+- **OAuth scope 会收窄工具集。**Token 携带 `data:read`、`data:write` 和 `actions:execute` 这些 scope——不在已授予 scope 内的工具,在该会话中根本不会被注册。API Key 和会话调用者获得完整工具集,但每次调用仍会做权限检查。
+- **操作体一经调用即作为可信应用代码运行**(`ai.exposed` 门槛和 `requiredPermissions` 在*调用*时检查)。把编写操作当作值得代码评审的行为——那才是真正的安全边界。
+- 操作还可以声明 `ai.requiresConfirmation`;看起来具有破坏性的操作默认要求确认。
+
+## 验证连接
+
+问 Agent 一个只有实时 schema 才能回答的问题:
+
+```text
+What objects does this app have, and what fields does the main one carry?
+```
+
+你应该看到 `list_objects` 和 `describe_object` 被触发。Agent 的自然工作模式是 `list_objects` → `describe_object` → `query_records` → `run_action`——四者都能跑通,连接就完全就绪了。
+
+> 配上应用的 **skill 文件**,Agent 的表现会明显更好:从 `GET /api/v1/mcp/skill` 下载它,或安装[官方 Claude 插件](https://github.com/objectstack-ai/claude-plugin)(`claude plugin marketplace add objectstack-ai/claude-plugin`),后者打包了该 skill 和一个引导式的 `/objectstack:connect` 命令。
+
+## 故障排查
+
+| 症状 | 原因 → 解决 |
+|---|---|
+| `/api/v1/mcp` 返回 `404` | 入口被禁用——取消设置 `OS_MCP_SERVER_ENABLED`(默认开启) |
+| `501 Not Implemented` | 此构建不包含 MCP 插件——检查你的栈的插件配置 |
+| 每次调用都 `401` | 匿名或凭据无效。交互式客户端:完成浏览器登录。无头场景:检查 `osk_` Key 和请求头拼写 |
+| `403 insufficient_scope` | OAuth token 缺少该工具族所需的 scope(例如没有 `data:write` 却尝试写入)——重新连接并授予该 scope |
+| 某个操作没出现在 `list_actions` 中 | `ai.exposed` 不为 `true`、`ai.description` 短于 40 个字符、类型不可无头调用(`url` / `modal` / `form` 永远不会出现)、目标是 `sys_*` 对象,或调用者未通过其 `requiredPermissions` |
+| 读取返回的行很少 / 写入被拒 | 符合设计——调用者的权限和记录访问在生效。用同一用户在 UI 中验证 |
+
+## 下一步
+
+| 任务 | 页面 |
+|---|---|
+| 配置 AI Provider、Embedder 和 MCP 服务器插件 | [AI 服务](/docs/configure/ai) |
+| 创建服务用户和 API Key | [用户与组织](/docs/configure/users) |
+| 理解 Agent 被允许看到什么 | [权限](/docs/configure/permissions) |
+| REST API 与 Key 管理 | [API 访问](/docs/configure/api-access) |
+| 验证某个用户的访问权限 | [权限分配](/docs/configure/permissions/managing-access) |
diff --git a/content/docs/configure/permissions/field-level-security.zh-Hans.mdx b/content/docs/configure/permissions/field-level-security.zh-Hans.mdx
new file mode 100644
index 0000000..4ca3749
--- /dev/null
+++ b/content/docs/configure/permissions/field-level-security.zh-Hans.mdx
@@ -0,0 +1,110 @@
+---
+title: 字段级安全
+description: 隐藏或锁定单个字段 —— 授予语义、服务端强制执行,以及 FLS 在表单、视图和 API 中的行为。
+---
+
+# 字段级安全
+
+字段级安全(FLS)控制单个字段的可见性与可编辑性,作用于对象权限和[记录访问](/docs/configure/permissions/record-access)已经允许用户触达该记录*之后*。它是实现"支持人员能看到客户,但看不到其 `annual_revenue`"和"销售代表能读外部 id 但永远不能改"的那一层。
+
+FLS 规则存在[权限集](/docs/configure/permissions/permission-sets)中——本页比那里的[字段安全附录](/docs/configure/permissions/permission-sets#field-security-appendix)更深入地讲解授予语义与强制执行。
+
+## 字段权限授予
+
+字段权限以 `