diff --git a/content/docs/build/automation/approvals.mdx b/content/docs/build/automation/approvals.mdx index 78bf474..bbd9638 100644 --- a/content/docs/build/automation/approvals.mdx +++ b/content/docs/build/automation/approvals.mdx @@ -57,15 +57,65 @@ The node declares **who approves** and how their decisions aggregate: | Config | What it does | |---|---| | `approvers` | Who must decide — a named user, a position, or a user resolved from a record field (e.g. the submitter's manager) | -| `behavior` | How multiple approvers aggregate, e.g. `'unanimous'` | +| `behavior` | How multiple approvers aggregate: `'first_response'` (default), `'unanimous'`, `'quorum'`, `'per_group'` | +| `minApprovals` | Approvals required — total for `quorum` (M of N), per group for `per_group` (default 1) | | `approvalStatusField` | Optional record field the plugin mirrors the request status into | | `lockRecord` | Lock the record against edits while the request is pending | -> **Warning — `position` vs `role`.** `{ type: 'position', value: 'finance_manager' }` -> routes to the holders of a position (`sys_user_position`). The `role` approver -> type is the org-membership tier (`sys_member.role`: `owner`/`admin`/`member`) — -> a position name authored as `type: 'role'` matches nobody and the request -> stalls. `os lint` flags this (`approval-role-not-membership-tier`). +> **Warning — `position` vs the membership tier.** +> `{ type: 'position', value: 'finance_manager' }` routes to the holders of a +> position (`sys_user_position`). The org-membership tier (`sys_member.role`: +> `owner`/`admin`/`member`) is addressed as `type: 'org_membership_level'` +> since 16.0; the old spelling `role` is a **deprecated alias for one +> release** — it still loads and resolves identically, with a warning, and is +> removed in the next major. A position name authored as the membership-tier +> type matches nobody and the request stalls. `os lint` flags both cases +> (`approval-approver-not-membership-tier`, +> `approval-approver-type-deprecated`); if the value names an org position, +> the fix is `type: 'position'`. + +### Quorum and per-group sign-off (16.0) + +Beyond `first_response` and `unanimous`, 16.0 adds two aggregation modes. +**Quorum** finalizes on M-of-N approvals: + +```ts +config: { + approvers: [ + { type: 'user', value: 'director_a' }, + { type: 'user', value: 'director_b' }, + { type: 'user', value: 'director_c' }, + ], + behavior: 'quorum', + minApprovals: 2, // any 2 of the 3 approve +} +``` + +**Per-group** requires a sign-off from *each* labeled group (会签) — label +approvers with `group`, and the node advances only once every group has +`minApprovals` approvals (default 1 per group): + +```ts +config: { + approvers: [ + { type: 'position', value: 'legal_counsel', group: 'legal' }, + { type: 'position', value: 'finance_manager', group: 'finance' }, + ], + behavior: 'per_group', // one sign-off from legal AND one from finance +} +``` + +Semantics that hold across every mode: + +- Approver sets are tallied against an **open-time snapshot** with + out-of-office substitution — who must respond is fixed when the request + opens, and OOO approvers are substituted rather than stalling it. +- A **single rejection is still a veto**: it finalizes the node as + `rejected` in every mode. +- Thresholds **clamp** at runtime to the resolvable approver count, so a + misconfigured `minApprovals` can never deadlock a request. +- Approvers without a `group` label each form their own group, so a plain + approver list still behaves sensibly under `per_group`. ## A complete approval flow @@ -133,7 +183,23 @@ While a request is pending: 3. If you set `approvalStatusField`, the record's own field mirrors the request status, so views and reports can filter on it. 4. The approver approves or rejects; the plugin resumes the paused flow down - the matching edge. + the matching edge. Since 16.0 a decision can carry **file attachments** + (persisted on `sys_approval_action.attachments`, threaded through the + decide/comment routes and the client SDK) — signed PDFs and evidence live + with the decision. +5. The request exposes a server-computed **`decision_progress`** — + approvals got vs. needed for `unanimous`/`quorum`, a per-group breakdown + for `per_group` — so the inbox and your own UIs render progress without + re-implementing the tally. + +**Decisions are declared actions (16.0).** The full decision set — approve, +reject, reassign, send-back for revision (`/revise`), request-info, remind, +recall, resubmit — is declared as `type: 'api'` actions on +`sys_approval_request`, with typed params, pending-only visibility gates, and +submitter-vs-approver predicates (e.g. `record.submitter_id == ctx.user.id`). +The Console approvals inbox renders those declared actions instead of +hand-written buttons, so new decision capabilities ship as metadata — no +client release required. Approving is itself a gated action. Model "may approve" as a capability (e.g. `approve_invoice`) granted by the approver's permission set, and gate the diff --git a/content/docs/build/automation/approvals.zh-Hans.mdx b/content/docs/build/automation/approvals.zh-Hans.mdx index 3d1e129..701bf0b 100644 --- a/content/docs/build/automation/approvals.zh-Hans.mdx +++ b/content/docs/build/automation/approvals.zh-Hans.mdx @@ -51,11 +51,47 @@ description: 把记录路由给人签核 —— 同时不让自动化悄悄绕 | 配置 | 作用 | |---|---| | `approvers` | 谁必须决策 —— 具名用户、岗位,或从记录字段解析出的用户(如提交人的经理) | -| `behavior` | 多个审批人如何聚合,如 `'unanimous'`(一致同意) | +| `behavior` | 多个审批人如何聚合:`'first_response'`(默认)、`'unanimous'`、`'quorum'`、`'per_group'` | +| `minApprovals` | 所需批准数 —— `quorum` 为总数(N 中取 M),`per_group` 为每组所需数(默认 1) | | `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`)。 +> **警告 —— `position` 与成员层级。**`{ type: 'position', value: 'finance_manager' }` 路由给某个岗位的持有者(`sys_user_position`)。组织成员层级(`sys_member.role`:`owner`/`admin`/`member`)自 16.0 起写作 `type: 'org_membership_level'`;旧写法 `role` 是**保留一个版本的弃用别名** —— 仍能加载并按相同方式解析,但会给出警告,下个大版本移除。把岗位名写成成员层级类型谁也匹配不上,请求就会卡住。`os lint` 对两种情况都会标记(`approval-approver-not-membership-tier`、`approval-approver-type-deprecated`);如果值是岗位名,正确写法是 `type: 'position'`。 + +### 法定人数与会签(16.0) + +在 `first_response` 和 `unanimous` 之外,16.0 新增两种聚合模式。**Quorum(法定人数)**在达到 N 中取 M 个批准时定案: + +```ts +config: { + approvers: [ + { type: 'user', value: 'director_a' }, + { type: 'user', value: 'director_b' }, + { type: 'user', value: 'director_c' }, + ], + behavior: 'quorum', + minApprovals: 2, // 3 人中任意 2 人批准 +} +``` + +**Per-group(会签)**要求*每个*带标签的组都签核 —— 用 `group` 给审批人打标签,只有当每个组都达到 `minApprovals` 个批准(默认每组 1 个)时节点才会前进: + +```ts +config: { + approvers: [ + { type: 'position', value: 'legal_counsel', group: 'legal' }, + { type: 'position', value: 'finance_manager', group: 'finance' }, + ], + behavior: 'per_group', // 法务一个签核,且财务一个签核 +} +``` + +对所有模式都成立的语义: + +- 审批人集合按**开启时快照**统计,并带休假(OOO)替补 —— 谁必须响应在请求开启时就已确定,休假的审批人会被替补而不是卡住请求。 +- **单个拒绝仍然是一票否决**:在任何模式下它都把节点定案为 `rejected`。 +- 阈值在运行时**收拢(clamp)**到可解析的审批人数量,因此配置错误的 `minApprovals` 永远不会让请求死锁。 +- 没有 `group` 标签的审批人各自成组,所以普通的审批人列表在 `per_group` 下依然行为合理。 ## 一个完整的审批流程 @@ -114,7 +150,10 @@ export const opportunityApproval = defineFlow({ 1. 插件持久化请求(`sys_approval_request`)以及针对它的每个决策(`sys_approval_action`)—— 你的审批历史是可查询的数据。 2. 设置 `lockRecord: true` 时,记录被锁定、禁止编辑,直到决策落地。 3. 如果设置了 `approvalStatusField`,记录自身的字段会镜像请求状态,视图和报表就能按它筛选。 -4. 审批人批准或拒绝;插件沿匹配的边恢复被暂停的流程。 +4. 审批人批准或拒绝;插件沿匹配的边恢复被暂停的流程。自 16.0 起,决策可以携带**文件附件**(持久化在 `sys_approval_action.attachments`,贯通决策/评论路由与客户端 SDK)—— 签署的 PDF 和证据材料与决策存放在一起。 +5. 请求暴露服务端计算的 **`decision_progress`** —— `unanimous`/`quorum` 为已获批准数 vs. 所需数,`per_group` 为按组明细 —— 收件箱和你自己的 UI 直接渲染进度,无需重新实现计票。 + +**决策即声明式操作(16.0)。**完整的决策集合 —— 批准、拒绝、转办、退回修改(`/revise`)、补充材料、催办、撤回、重新提交 —— 以 `type: 'api'` 操作的形式声明在 `sys_approval_request` 上,带类型化参数、仅待定时可见的门控,以及提交人 vs 审批人谓词(如 `record.submitter_id == ctx.user.id`)。Console 审批收件箱渲染的是这些声明的操作,而不是手写按钮,因此新的决策能力以元数据形式发布 —— 无需客户端发版。 批准本身也是被门控的操作。把"可以批准"建模为一个能力(如 `approve_invoice`),由审批人的权限集授予,并把批准操作的 `requiredPermissions` 建立在它之上 —— 这样门控就在 **UI 和服务端**两侧同时强制,而不只是从屏幕上藏起来。 diff --git a/content/docs/build/automation/flows.mdx b/content/docs/build/automation/flows.mdx index c6ba0fa..abec278 100644 --- a/content/docs/build/automation/flows.mdx +++ b/content/docs/build/automation/flows.mdx @@ -26,12 +26,13 @@ export default defineStack({ }); ``` -## Three flow types +## Flow types | Type | Triggered by | Use for | |---|---|---| | **Autolaunched** | A record change (insert/update/delete) | "Send welcome email when user registers" | | **Scheduled** | Cron expression or interval | "Mark stale tasks every night at 2am" | +| **Time-relative** *(16.0)* | A date field's proximity to today, via a daily sweep | "Remind me 60 days before the contract ends" | | **Manual** | User clicks a button in Console, or an API call | "Approve invoice" actions | ## Autolaunched: react to a record change @@ -107,6 +108,80 @@ export const nightlyCleanup = defineFlow({ Backed by the `@objectstack/service-job` capability — see [Runtime Capabilities](/docs/reference/runtime-capabilities). +## Time-relative: fire relative to a date field (16.0) + +"Remind me 60 days before the contract ends" used to be authored as a +record-change flow gated on `record.end_date == daysFromNow(60)` — a +predicate that is only evaluated when the record *happens to change*, so +unattended it fires almost never. Don't write that. Declare a +**time-relative trigger** instead: the flow's start node carries a +`config.timeRelative` descriptor, and the runtime sweeps the object on a +schedule and launches the flow **once per matching record**: + +```ts +export const renewalReminder = defineFlow({ + name: 'contract_renewal_reminder', + type: 'schedule', + status: 'active', + nodes: [ + { + id: 'start', + type: 'start', + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus thresholds + filter: { status: 'active' }, // AND-ed with the date window + }, + // Optional sweep cadence — defaults to daily at 08:00 UTC. + schedule: { type: 'cron', expression: '0 8 * * *' }, + }, + }, + { id: 'notify_owner', type: 'notify', label: 'Notify Owner' }, + { id: 'end', type: 'end' }, + ], + edges: [ + { id: 'e1', source: 'start', target: 'notify_owner' }, + { id: 'e2', source: 'notify_owner', target: 'end' }, + ], +}); +``` + +| Key | What it declares | +|---|---| +| `object` | Object (machine name) whose records the sweep queries | +| `dateField` | `date` / `datetime` field evaluated relative to today (day-granular) | +| `offsetDays` | **Offset mode** — fire when `dateField` is exactly today + each listed offset (`[60, 30, 7]`; negative = past, e.g. `[-1]` the day after) | +| `withinDays` | **Range mode** — fire every day `dateField` is within N days of today: positive = upcoming ("expiring soon"), negative = bounded overdue lookback, `0` = due today | +| `filter` | Optional ObjectQL where-map AND-ed with the computed date window (e.g. `{ status: 'active' }`) | +| `maxRecords` | Cap on records launched per sweep (default 1000; the sweep logs when it clamps) | + +Exactly **one** of `offsetDays` or `withinDays` must be set. The two other +common shapes: + +```ts +// "Expiring soon" — fires every day a document is within 30 days of expiry. +timeRelative: { object: 'hr_document', dateField: 'expires_on', withinDays: 30 } + +// Overdue sweep — fires for POs up to 14 days past due (bounded lookback). +timeRelative: { object: 'purchase_order', dateField: 'due_date', + withinDays: -14, filter: { status: 'open' } } +``` + +Each launch puts the matching record on the automation context, so the +start-node `condition` and `{record.}` interpolation work exactly +as they do for record-change flows. The discovery query runs as system, +with per-record failure isolation. `os validate` gains readiness checks — +it warns when `timeRelative.object` names an object the stack doesn't +define, and when an auto-triggered flow is left in `draft` status. + +> **Genuine same-day checks are fine now (#3183).** In 16.0 +> `record.due_date == today()` matches — the engine coerces temporal +> `==` / `!=` comparisons so a date field compares correctly against +> `today()`. Use that for a real "due today" condition; use +> `timeRelative` for anything of the form "N days before/after a date". + ## Manual: actions and approvals ```ts diff --git a/content/docs/build/automation/flows.zh-Hans.mdx b/content/docs/build/automation/flows.zh-Hans.mdx index 467c252..c2f833b 100644 --- a/content/docs/build/automation/flows.zh-Hans.mdx +++ b/content/docs/build/automation/flows.zh-Hans.mdx @@ -20,12 +20,13 @@ export default defineStack({ }); ``` -## 三种流程类型 +## 流程类型 | 类型 | 触发方式 | 用于 | |---|---|---| | **Autolaunched** | 记录变更(insert/update/delete) | "用户注册时发欢迎邮件" | | **Scheduled** | Cron 表达式或间隔 | "每晚 2 点标记过期任务" | +| **Time-relative** *(16.0)* | 日期字段与今天的距离,经由每日扫描 | "合同到期前 60 天提醒我" | | **Manual** | 用户在 Console 点按钮,或 API 调用 | "审批发票"等 Action | ## Autolaunched:对记录变更做出反应 @@ -96,6 +97,64 @@ export const nightlyCleanup = defineFlow({ 由 `@objectstack/service-job` 能力承载 —— 见 [Runtime Capabilities](/docs/reference/runtime-capabilities)。 +## Time-relative:相对日期字段触发(16.0) + +"合同到期前 60 天提醒我"以前只能写成一个用 `record.end_date == daysFromNow(60)` 做门控的记录变更流程 —— 这个谓词只有在记录*恰好被修改*时才会求值,无人照看时几乎永远不会触发。别这么写。改为声明**时间相对触发器**:流程的开始节点携带一个 `config.timeRelative` 描述符,运行时按计划扫描该对象,并为**每条匹配记录各启动一次**流程: + +```ts +export const renewalReminder = defineFlow({ + name: 'contract_renewal_reminder', + type: 'schedule', + status: 'active', + nodes: [ + { + id: 'start', + type: 'start', + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus thresholds + filter: { status: 'active' }, // AND-ed with the date window + }, + // Optional sweep cadence — defaults to daily at 08:00 UTC. + schedule: { type: 'cron', expression: '0 8 * * *' }, + }, + }, + { id: 'notify_owner', type: 'notify', label: 'Notify Owner' }, + { id: 'end', type: 'end' }, + ], + edges: [ + { id: 'e1', source: 'start', target: 'notify_owner' }, + { id: 'e2', source: 'notify_owner', target: 'end' }, + ], +}); +``` + +| 键 | 声明什么 | +|---|---| +| `object` | 扫描其记录的对象(机器名) | +| `dateField` | 相对今天求值的 `date` / `datetime` 字段(按天粒度) | +| `offsetDays` | **偏移模式** —— 当 `dateField` 恰好等于今天 + 所列各偏移时触发(`[60, 30, 7]`;负数 = 过去,如 `[-1]` 表示次日) | +| `withinDays` | **区间模式** —— `dateField` 落在距今天 N 天内的每一天都触发:正数 = 即将到来("即将过期"),负数 = 有界的逾期回看,`0` = 今天到期 | +| `filter` | 可选的 ObjectQL where 映射,与计算出的日期窗口做 AND(如 `{ status: 'active' }`) | +| `maxRecords` | 每次扫描启动流程的记录数上限(默认 1000;触发收拢时扫描会记日志) | + +`offsetDays` 与 `withinDays` 必须**恰好设置一个**。另外两种常见形态: + +```ts +// "Expiring soon" — fires every day a document is within 30 days of expiry. +timeRelative: { object: 'hr_document', dateField: 'expires_on', withinDays: 30 } + +// Overdue sweep — fires for POs up to 14 days past due (bounded lookback). +timeRelative: { object: 'purchase_order', dateField: 'due_date', + withinDays: -14, filter: { status: 'open' } } +``` + +每次启动都会把匹配记录放到自动化上下文中,因此开始节点的 `condition` 和 `{record.}` 插值与记录变更流程完全一致。发现查询以 system 身份运行,并做按记录的故障隔离。`os validate` 新增就绪性检查 —— 当 `timeRelative.object` 指向 stack 未定义的对象、或自动触发的流程停留在 `draft` 状态时都会警告。 + +> **真正的"当天"检查现在没问题了(#3183)。**16.0 中 `record.due_date == today()` 能匹配了 —— 引擎会对时间类 `==` / `!=` 比较做强制转换,让日期字段与 `today()` 正确比较。真正的"今天到期"条件用它;而任何"某日期前/后 N 天"形态的需求,用 `timeRelative`。 + ## Manual:Action 与审批 ```ts diff --git a/content/docs/build/automation/workflows.mdx b/content/docs/build/automation/workflows.mdx index 160e6d3..d50cd67 100644 --- a/content/docs/build/automation/workflows.mdx +++ b/content/docs/build/automation/workflows.mdx @@ -137,15 +137,19 @@ If you're coming from a platform with Workflow Rules, here's the mapping: | Old concept | Current equivalent | |---|---| | Workflow Rule | Flow | -| Time trigger | Scheduled flow | +| Time trigger ("N days before/after a date") | Time-relative trigger — `config.timeRelative` on a flow's start node (16.0, see [Flows](/docs/build/automation/flows)) | +| Time trigger (fixed clock) | Scheduled flow | | Field update action | `update_record` node | | Email alert | `notify` node | | HTTP call | `http` node | | Approval Process | Flow with one or more `approval` nodes | The test: if the old rule was "when X happens, do Y", model it as a small -flow. If it was "this record must move through controlled states", model the -lifecycle as a state machine and use flows for the side effects. +flow. If it was "N days before/after a date field, do Y", declare a +time-relative trigger — never a date-equality condition on a record-change +flow, which only evaluates when the record happens to change. If it was +"this record must move through controlled states", model the lifecycle as a +state machine and use flows for the side effects. ## Where to go next diff --git a/content/docs/build/automation/workflows.zh-Hans.mdx b/content/docs/build/automation/workflows.zh-Hans.mdx index 0e87d6e..698edcf 100644 --- a/content/docs/build/automation/workflows.zh-Hans.mdx +++ b/content/docs/build/automation/workflows.zh-Hans.mdx @@ -118,13 +118,14 @@ export const dealClosedWon = defineFlow({ | 旧概念 | 当前对应 | |---|---| | Workflow Rule | 流程 | -| 时间触发器 | 定时流程 | +| 时间触发器("某日期前/后 N 天") | 时间相对触发器 —— 流程开始节点上的 `config.timeRelative`(16.0,见[流程](/docs/build/automation/flows)) | +| 时间触发器(固定时钟) | 定时流程 | | 字段更新动作 | `update_record` 节点 | | 邮件提醒 | `notify` 节点 | | HTTP 调用 | `http` 节点 | | Approval Process | 带一个或多个 `approval` 节点的流程 | -判断标准:如果旧规则是"X 发生时,做 Y",就建成一个小流程。如果它是"这条记录必须在受控状态间移动",就把生命周期建成状态机,副作用交给流程。 +判断标准:如果旧规则是"X 发生时,做 Y",就建成一个小流程。如果它是"某日期字段前/后 N 天,做 Y",就声明时间相对触发器 —— 千万别在记录变更流程上写日期相等条件,那种条件只有在记录恰好被修改时才会求值。如果它是"这条记录必须在受控状态间移动",就把生命周期建成状态机,副作用交给流程。 ## 下一步 diff --git a/content/docs/build/data/formulas.mdx b/content/docs/build/data/formulas.mdx index f4967f5..8a0559d 100644 --- a/content/docs/build/data/formulas.mdx +++ b/content/docs/build/data/formulas.mdx @@ -99,6 +99,22 @@ Field.formula({ > `coalesce(record.x, '')` when concatenating — or use `joinNonEmpty` > and skip the ceremony. +### New in 16.0 + +- **Date arithmetic against numbers is a build-time error.** Expressions + like `record.end_date - record.start_date + 1` or `today() + 30` + always faulted at runtime (silently evaluating to null); the build now + rejects them with an error pointing at `daysBetween(a, b)` for spans + and `daysFromNow(n)` / `addDays(d, n)` / `addMonths(d, n)` for shifts. +- **The null-guard ternary compiles.** `cond ? value : null` — e.g. + `record.end_date != null ? daysBetween(record.start_date, record.end_date) + 1 : null` + — is the blessed way to keep a formula blank until its inputs exist. +- **`floor(x)` / `ceil(x)` are registered.** They round toward −∞ / +∞ + (so `floor(-1.2) == -2`) — not toward zero like integer division. +- **Date equality against `today()` now matches.** Temporal values are + coerced before comparison, so `record.due_date == today()` and + `daysBetween(today(), record.due_date) == 0` both work as expected. + ### Formula as record title Since ADR-0079, a record's title is a designated field via `nameField` diff --git a/content/docs/build/data/formulas.zh-Hans.mdx b/content/docs/build/data/formulas.zh-Hans.mdx index 7f71277..5b99fe1 100644 --- a/content/docs/build/data/formulas.zh-Hans.mdx +++ b/content/docs/build/data/formulas.zh-Hans.mdx @@ -87,6 +87,13 @@ Field.formula({ > **提示:**CEL 遇到 `null + string` 会抛错,所以拼接时把可能为空的操作数包进 `coalesce(record.x, '')` —— 或者直接用 `joinNonEmpty`,省掉这套仪式。 +### 16.0 新增 + +- **日期与数字做算术现在是构建期错误。**像 `record.end_date - record.start_date + 1` 或 `today() + 30` 这样的表达式过去总在运行时出错(静默求值为 null);现在构建会直接拒绝,并在报错中指向 `daysBetween(a, b)`(求跨度)以及 `daysFromNow(n)` / `addDays(d, n)` / `addMonths(d, n)`(移动日期)。 +- **空值守卫三元表达式可以编译了。**`cond ? value : null` —— 例如 `record.end_date != null ? daysBetween(record.start_date, record.end_date) + 1 : null` —— 是让公式在输入就绪前保持空白的推荐写法。 +- **注册了 `floor(x)` / `ceil(x)`。**它们分别向 −∞ / +∞ 取整(所以 `floor(-1.2) == -2`)—— 不像整数除法那样向零取整。 +- **日期与 `today()` 的相等比较现在能匹配了。**时间值会在比较前先做类型归一,因此 `record.due_date == today()` 和 `daysBetween(today(), record.due_date) == 0` 都能按预期工作。 + ### 公式作为记录标题 自 ADR-0079 起,记录的标题由 `nameField` 指定的字段决定 —— 组合式标题可以用文本公式: diff --git a/content/docs/build/data/index.de.mdx b/content/docs/build/data/index.de.mdx index 09cf6d3..3040e83 100644 --- a/content/docs/build/data/index.de.mdx +++ b/content/docs/build/data/index.de.mdx @@ -216,7 +216,7 @@ ObjectSchema.create({ apiEnabled: true, // generated REST endpoints trackHistory: true, // audit log of field changes feeds: true, // sys_comment / sys_activity / @mentions - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` diff --git a/content/docs/build/data/index.es.mdx b/content/docs/build/data/index.es.mdx index 81f7e48..e002c4d 100644 --- a/content/docs/build/data/index.es.mdx +++ b/content/docs/build/data/index.es.mdx @@ -215,7 +215,7 @@ ObjectSchema.create({ apiEnabled: true, // generated REST endpoints trackHistory: true, // audit log of field changes feeds: true, // sys_comment / sys_activity / @mentions - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` diff --git a/content/docs/build/data/index.fr.mdx b/content/docs/build/data/index.fr.mdx index 28f8bba..f9bd719 100644 --- a/content/docs/build/data/index.fr.mdx +++ b/content/docs/build/data/index.fr.mdx @@ -217,7 +217,7 @@ ObjectSchema.create({ apiEnabled: true, // generated REST endpoints trackHistory: true, // audit log of field changes feeds: true, // sys_comment / sys_activity / @mentions - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` diff --git a/content/docs/build/data/index.ja.mdx b/content/docs/build/data/index.ja.mdx index 0763397..3525bd1 100644 --- a/content/docs/build/data/index.ja.mdx +++ b/content/docs/build/data/index.ja.mdx @@ -216,7 +216,7 @@ ObjectSchema.create({ apiEnabled: true, // generated REST endpoints trackHistory: true, // audit log of field changes feeds: true, // sys_comment / sys_activity / @mentions - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` diff --git a/content/docs/build/data/index.ko.mdx b/content/docs/build/data/index.ko.mdx index 2f2aaf4..405f328 100644 --- a/content/docs/build/data/index.ko.mdx +++ b/content/docs/build/data/index.ko.mdx @@ -214,7 +214,7 @@ ObjectSchema.create({ apiEnabled: true, // generated REST endpoints trackHistory: true, // audit log of field changes feeds: true, // sys_comment / sys_activity / @mentions - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` diff --git a/content/docs/build/data/index.mdx b/content/docs/build/data/index.mdx index f64e062..db53d9e 100644 --- a/content/docs/build/data/index.mdx +++ b/content/docs/build/data/index.mdx @@ -217,11 +217,19 @@ ObjectSchema.create({ feeds: true, // sys_comment / @mentions (default true) activities: true, // mirror CRUD to sys_activity timeline (default true) files: true, // Attachments panel (opt-in, default false) - softDelete: true, // tombstone instead of hard delete + trash: true, // soft-delete with restore (default true) }, }); ``` +> **Changed in 16.0:** the object-level `softDelete` prop is removed — +> `ObjectSchema.create` now throws a located error if you pass it. The +> shipped equivalent is `enable: { trash: true }` (soft-delete with +> restore), on by default. The same cleanup removed `versioning`, +> `search` (use top-level `searchableFields`), `recordName` (use an +> autonumber field as `nameField`), `keyPrefix`, `tags`, `active`, and +> `abstract`. + Since ObjectStack 14 the `enable.*` flags are **enforced**, not advisory: `feeds: false` rejects comment creation with 403 `FEEDS_DISABLED`, `files` must be opted in before `sys_attachment` rows diff --git a/content/docs/build/data/index.zh-Hans.mdx b/content/docs/build/data/index.zh-Hans.mdx index 16dbe3f..fa05277 100644 --- a/content/docs/build/data/index.zh-Hans.mdx +++ b/content/docs/build/data/index.zh-Hans.mdx @@ -208,11 +208,18 @@ ObjectSchema.create({ feeds: true, // sys_comment / @提及(默认 true) activities: true, // CRUD 镜像到 sys_activity 时间线(默认 true) files: true, // 附件面板(需显式开启,默认 false) - softDelete: true, // 软删除而非硬删除 + trash: true, // 软删除并可恢复(默认 true) }, }); ``` +> **16.0 变更:**对象级 `softDelete` 属性已被移除 —— 再传入它, +> `ObjectSchema.create` 会抛出带定位信息的错误。对应的现行能力是 +> `enable: { trash: true }`(软删除并可恢复),默认开启。同一轮清理还 +> 移除了 `versioning`、`search`(改用顶层 `searchableFields`)、 +> `recordName`(改用作为 `nameField` 的自动编号字段)、`keyPrefix`、 +> `tags`、`active` 和 `abstract`。 + 自 ObjectStack 14 起,`enable.*` 开关是**强制执行**的,而不再只是声明: `feeds: false` 会以 403 `FEEDS_DISABLED` 拒绝评论创建;`files` 必须显式开启 后才能创建 `sys_attachment` 行(否则返回 403 `FILES_DISABLED`); diff --git a/content/docs/build/data/relationships.mdx b/content/docs/build/data/relationships.mdx index 861bbbd..8feee8c 100644 --- a/content/docs/build/data/relationships.mdx +++ b/content/docs/build/data/relationships.mdx @@ -68,9 +68,10 @@ contact: Field.lookup('contact', { ``` > **Warning:** The legacy `referenceFilters: string[]` property (e.g. -> `['is_active = true']`) is accepted by the schema but **not read by -> the record-picker UI** — it filters nothing. Always use the -> structured `lookupFilters` + `dependsOn` shown above. +> `['is_active = true']`) is **removed in 16.0** — it is stripped at +> parse time, and even before removal the record-picker UI never read +> it, so it filtered nothing. The structured `lookupFilters` + +> `dependsOn` shown above is the only supported form. ## Master-detail (owned children) @@ -167,6 +168,7 @@ total_lines: { object: 'order_line', // child object to aggregate field: 'quantity', // field to aggregate function: 'sum', // count | sum | min | max | avg + filter: { status: 'active' }, // new in 16.0: aggregate only matching children }, } ``` @@ -174,6 +176,12 @@ total_lines: { Summary fields are read-only and computed from the children — you never write them directly. +**New in 16.0:** the optional `filter` is a query-style `where` +predicate — only child rows matching it are aggregated (e.g. count only +`status == 'active'` children), and the roll-up re-aggregates when a +child moves in or out of the predicate. It also lets several summaries +roll up the *same* child object into different totals. + ## Querying across relationships Load related records in one call with `expand` — the query API follows diff --git a/content/docs/build/data/relationships.zh-Hans.mdx b/content/docs/build/data/relationships.zh-Hans.mdx index 6f68208..142c57a 100644 --- a/content/docs/build/data/relationships.zh-Hans.mdx +++ b/content/docs/build/data/relationships.zh-Hans.mdx @@ -59,7 +59,7 @@ contact: Field.lookup('contact', { }) ``` -> **警告:**旧的 `referenceFilters: string[]` 属性(如 `['is_active = true']`)虽然能通过 Schema 校验,但**记录选择器 UI 并不读取它** —— 它什么都不过滤。请始终使用上面展示的结构化 `lookupFilters` + `dependsOn`。 +> **警告:**旧的 `referenceFilters: string[]` 属性(如 `['is_active = true']`)**已在 16.0 中移除** —— 解析时会被直接剥离;而且即使在移除之前,记录选择器 UI 也从未读取过它,它什么都不过滤。上面展示的结构化 `lookupFilters` + `dependsOn` 是唯一受支持的形式。 ## 主从(有归属的子记录) @@ -143,12 +143,18 @@ total_lines: { object: 'order_line', // 要聚合的子对象 field: 'quantity', // 要聚合的字段 function: 'sum', // count | sum | min | max | avg + filter: { status: 'active' }, // 16.0 新增:只聚合匹配的子记录 }, } ``` Summary 字段是只读的,由子记录计算得出 —— 你永远不会直接写它。 +**16.0 新增:**可选的 `filter` 是一个查询式 `where` 谓词 —— 只有匹配它 +的子记录才会被聚合(例如只统计 `status == 'active'` 的子记录);当某条 +子记录进入或离开该谓词范围时,汇总会重新聚合。它还让多个 summary 字段 +可以对*同一个*子对象汇总出不同的总计。 + ## 跨关系查询 用 `expand` 在一次调用里加载关联记录 —— 查询 API 会顺着查找字段把被引用的记录内联进来,客户端不必发出 N+1 次请求。 diff --git a/content/docs/build/data/validation-rules.mdx b/content/docs/build/data/validation-rules.mdx index 76e20d6..a9450f6 100644 --- a/content/docs/build/data/validation-rules.mdx +++ b/content/docs/build/data/validation-rules.mdx @@ -5,7 +5,8 @@ description: Required fields, unique constraints, and CEL-powered rules that sto **Validation runs on every write path.** REST, Console forms, ObjectQL — the same rules fire everywhere, so there is no back door for bad -data. A validation rule is a deterministic, synchronous, side-effect-free +data. Since 16.0 that includes multi-row updates: a bulk update +evaluates the rules once per matched row. A validation rule is a deterministic, synchronous, side-effect-free predicate over a single record: decidable from the incoming write (and, on update, the prior record) with no I/O. @@ -18,7 +19,7 @@ Validation is layered — use the lowest layer that can express the rule: | **Conditional modifiers** | Context-dependent presence | ``requiredWhen: P`record.amount > 10000` `` | | **Object validation rules** | Cross-field business logic | "Discount cannot exceed total" | | **Unique indexes** | Composite / scoped uniqueness | `{ fields: ['code', 'organization'], unique: true }` | -| **Lifecycle hooks** | Arbitrary validation code | `beforeInsert` / `beforeUpdate` | +| **Lifecycle hooks** | Arbitrary validation code, delete guards | `beforeInsert` / `beforeUpdate` / `beforeDelete` | ## Field-level: required, unique, constraints @@ -106,7 +107,7 @@ Every rule type shares this base shape: | `message` | yes | User-facing error message | | `type` | yes | `script`, `state_machine`, `format`, `cross_field`, `json_schema`, `conditional` | | `severity` | no | `error` (blocks save, default), `warning` (allows save), `info` | -| `events` | no | `insert`, `update`, `delete` — default `['insert', 'update']` | +| `events` | no | `insert`, `update` — default `['insert', 'update']`. The `delete` event was removed in 16.0: the evaluator never ran on delete (a delete carries no record payload), so it was a silent no-op — guard deletions with a `beforeDelete` hook | | `priority` | no | 0–9999, lower runs first (default 100) | | `active` | no | Toggle without deleting (default `true`) | @@ -115,7 +116,7 @@ Every rule type shares this base shape: | Type | Checks | Key config | |---|---|---| | `script` | Any CEL predicate | `condition` (TRUE = invalid) | -| `state_machine` | Allowed status transitions | `field`, `transitions` map | +| `state_machine` | Allowed status transitions | `field`, `transitions` map, optional `initialStates` | | `format` | One field against regex or built-in format | `field`, `regex` or `format: 'email' \| 'url' \| 'phone' \| 'json'` | | `cross_field` | Relationship between fields | `fields`, `condition` | | `json_schema` | JSON field against a JSON Schema | `field`, `schema` | @@ -139,7 +140,8 @@ Two rules you'll reach for constantly: cancelled: [], completed: [], }, - events: ['update'], + initialStates: ['draft'], // new in 16.0: states a record may be CREATED in + events: ['insert', 'update'], // include 'insert' so initialStates is checked } // Cross-field — dates in order @@ -154,6 +156,15 @@ Two rules you'll reach for constantly: } ``` +**New in 16.0:** `state_machine` rules can declare `initialStates` — +the states a record may be *created* in. An insert whose state field +carries a value outside the list is rejected (`invalid_initial_state`). +`transitions` only governs updates, and a `select` field permits any +declared option as an initial value, so without `initialStates` a +record can be born mid-flow (e.g. created already `approved`). Omit it +to keep the legacy behavior (no initial-state check on insert). The +rule's `events` must include `insert` for the check to run. + In update-time conditions, `previous` holds the pre-change snapshot — `record.stage != previous.stage` detects a change. @@ -177,7 +188,7 @@ indexes: [ The same logic excludes async/remote validation (a client-form concern and an SSRF/latency hazard on the write path) and custom handlers — arbitrary validation code belongs in a `beforeInsert` / `beforeUpdate` -lifecycle hook. +lifecycle hook, and delete-time guards in a `beforeDelete` hook. ## Custom error messages diff --git a/content/docs/build/data/validation-rules.zh-Hans.mdx b/content/docs/build/data/validation-rules.zh-Hans.mdx index ba194fb..2c9ed7d 100644 --- a/content/docs/build/data/validation-rules.zh-Hans.mdx +++ b/content/docs/build/data/validation-rules.zh-Hans.mdx @@ -3,7 +3,7 @@ title: 验证规则 description: 必填字段、唯一约束与 CEL 驱动的规则,在平台层拦住坏数据 —— 错误消息由你掌控。 --- -**验证在每条写入路径上都会执行。**REST、Console 表单、ObjectQL —— 同一套规则处处生效,坏数据没有后门可钻。验证规则是作用于单条记录的确定性、同步、无副作用谓词:仅凭这次写入(更新时再加上更新前的记录)即可判定,不做任何 I/O。 +**验证在每条写入路径上都会执行。**REST、Console 表单、ObjectQL —— 同一套规则处处生效,坏数据没有后门可钻。自 16.0 起也包括多行更新:批量更新会对每一条匹配的记录逐行执行规则。验证规则是作用于单条记录的确定性、同步、无副作用谓词:仅凭这次写入(更新时再加上更新前的记录)即可判定,不做任何 I/O。 验证是分层的 —— 能表达规则的最低层就是该用的层: @@ -14,7 +14,7 @@ description: 必填字段、唯一约束与 CEL 驱动的规则,在平台层 | **条件修饰符** | 依赖上下文的必填 | ``requiredWhen: P`record.amount > 10000` `` | | **对象级验证规则** | 跨字段业务逻辑 | "折扣不能超过总额" | | **唯一索引** | 组合 / 限定范围的唯一性 | `{ fields: ['code', 'organization'], unique: true }` | -| **生命周期 Hook** | 任意验证代码 | `beforeInsert` / `beforeUpdate` | +| **生命周期 Hook** | 任意验证代码、删除守卫 | `beforeInsert` / `beforeUpdate` / `beforeDelete` | ## 字段级:required、unique 与约束 @@ -94,7 +94,7 @@ export const Order = ObjectSchema.create({ | `message` | 是 | 面向用户的错误消息 | | `type` | 是 | `script`、`state_machine`、`format`、`cross_field`、`json_schema`、`conditional` | | `severity` | 否 | `error`(阻止保存,默认)、`warning`(允许保存)、`info` | -| `events` | 否 | `insert`、`update`、`delete` —— 默认 `['insert', 'update']` | +| `events` | 否 | `insert`、`update` —— 默认 `['insert', 'update']`。`delete` 事件已在 16.0 中移除:求值器从未在删除路径上运行(删除没有可供验证的记录载荷),它一直是静默空操作 —— 删除守卫请用 `beforeDelete` Hook | | `priority` | 否 | 0–9999,数字越小越先执行(默认 100) | | `active` | 否 | 不删除即可开关(默认 `true`) | @@ -103,7 +103,7 @@ export const Order = ObjectSchema.create({ | 类型 | 检查什么 | 关键配置 | |---|---|---| | `script` | 任意 CEL 谓词 | `condition`(TRUE = 无效) | -| `state_machine` | 允许的状态迁移 | `field`、`transitions` 映射 | +| `state_machine` | 允许的状态迁移 | `field`、`transitions` 映射、可选 `initialStates` | | `format` | 单字段匹配正则或内置格式 | `field`、`regex` 或 `format: 'email' \| 'url' \| 'phone' \| 'json'` | | `cross_field` | 字段之间的关系 | `fields`、`condition` | | `json_schema` | JSON 字段匹配 JSON Schema | `field`、`schema` | @@ -127,7 +127,8 @@ export const Order = ObjectSchema.create({ cancelled: [], completed: [], }, - events: ['update'], + initialStates: ['draft'], // 16.0 新增:记录允许被“创建”为哪些状态 + events: ['insert', 'update'], // 包含 'insert' 才会检查 initialStates } // 跨字段 —— 日期先后有序 @@ -142,6 +143,8 @@ export const Order = ObjectSchema.create({ } ``` +**16.0 新增:**`state_machine` 规则可以声明 `initialStates` —— 记录允许被*创建*为哪些状态。插入时状态字段的值不在列表内会被拒绝(`invalid_initial_state`)。`transitions` 只约束更新,而 `select` 字段允许任何已声明的选项作为初始值,所以不加 `initialStates` 时记录可能"生在流程中间"(比如一创建就是 `approved`)。省略它则保持旧行为(插入时不检查初始状态)。规则的 `events` 必须包含 `insert`,该检查才会执行。 + 在更新时的条件里,`previous` 持有变更前的快照 —— `record.stage != previous.stage` 可检测到变更。 ## 唯一性:用索引,不用规则 @@ -159,7 +162,7 @@ indexes: [ ] ``` -同样的逻辑也排除了异步 / 远程验证(那是客户端表单的关注点,放在写路径上还是 SSRF 与延迟隐患)和自定义处理器 —— 任意验证代码应该放进 `beforeInsert` / `beforeUpdate` 生命周期 Hook。 +同样的逻辑也排除了异步 / 远程验证(那是客户端表单的关注点,放在写路径上还是 SSRF 与延迟隐患)和自定义处理器 —— 任意验证代码应该放进 `beforeInsert` / `beforeUpdate` 生命周期 Hook,删除时的守卫则放进 `beforeDelete` Hook。 ## 自定义错误消息 diff --git a/content/docs/build/interface/dashboards.mdx b/content/docs/build/interface/dashboards.mdx index 2f968f4..b4a9a64 100644 --- a/content/docs/build/interface/dashboards.mdx +++ b/content/docs/build/interface/dashboards.mdx @@ -81,9 +81,14 @@ automatically apply the caller's row-level security scope to the base object and joined objects — a dashboard never shows a user records their [permissions](/docs/configure/permissions) wouldn't. -> The legacy inline-query shape (`object` + `categoryField` + `valueField` -> + `aggregate` directly on the widget) was removed. Define a dataset and -> bind widgets to it. +> Since 16.0, the widget schema is **strict**: an undeclared top-level key +> — a typo, a hallucinated key, or a removed inline-query key (`object` + +> `categoryField` + `valueField` + `aggregate`, pivot `rowField` / +> `columnField`) — is a **parse error that names the offending key** and +> points at the dataset shape, no longer silently stripped into a widget +> that renders nothing. Define a dataset and bind widgets to it; +> `options` remains the free-form escape hatch for renderer-specific +> extras. ## Widgets diff --git a/content/docs/build/interface/dashboards.zh-Hans.mdx b/content/docs/build/interface/dashboards.zh-Hans.mdx index 1f31fb2..099a765 100644 --- a/content/docs/build/interface/dashboards.zh-Hans.mdx +++ b/content/docs/build/interface/dashboards.zh-Hans.mdx @@ -71,7 +71,7 @@ export const salesDataset = defineDataset({ 运行时,数据集查询经由分析服务执行,并自动把调用者的行级安全范围应用到基础对象和被连接的对象上 —— 仪表盘绝不会向用户展示其[权限](/docs/configure/permissions)不允许看的记录。 -> 旧的内联查询形态(在组件上直接写 `object` + `categoryField` + `valueField` + `aggregate`)已经移除。请定义数据集并把组件绑定上去。 +> 自 16.0 起,组件 Schema 是**严格**的:任何未声明的顶层键 —— 打错的键、幻觉出来的键,或已移除的内联查询键(`object` + `categoryField` + `valueField` + `aggregate`,以及透视表的 `rowField` / `columnField`)—— 都是**指名道姓的解析错误**,并指向数据集形态,而不再被悄悄剥掉、留下一个什么都不渲染的组件。请定义数据集并把组件绑定上去;`options` 仍是渲染器专属额外配置的自由格式逃生口。 ## 组件 diff --git a/content/docs/build/interface/pages.mdx b/content/docs/build/interface/pages.mdx index b03c24e..95e764f 100644 --- a/content/docs/build/interface/pages.mdx +++ b/content/docs/build/interface/pages.mdx @@ -16,7 +16,7 @@ const homePage = { regions: [ { name: 'header', width: 'full', components: [ { type: 'metric_card', id: 'total_revenue', label: 'Total Revenue', - properties: { object: 'opportunity', field: 'amount', aggregate: 'sum', format: 'currency' } }, + properties: { dataset: 'sales', values: ['revenue'], format: 'currency' } }, ]}, { name: 'main', width: 'large', components: [ { type: 'list_view', id: 'recent_deals', label: 'Recent Deals', @@ -75,13 +75,20 @@ Components are the building blocks inside regions: type: 'chart', id: 'revenue_chart', label: 'Revenue Trend', - properties: { chartType: 'line', object: 'opportunity', - categoryField: 'close_date', valueField: 'amount' }, + properties: { chartType: 'line', dataset: 'sales', + dimensions: ['close_month'], values: ['revenue'] }, events: { onClick: "navigate_to('opportunity_detail', { id: $event.id })" }, visibility: "os.user.profile == 'sales_manager'", } ``` +Analytics-bearing components (`chart`, `metric_card`) bind a named +**dataset** and select its `dimensions` + `values` by name — the same +ADR-0021 shape as [dashboard](/docs/build/interface/dashboards) widgets +and Studio's only output. The removed inline-analytics shape (`object` + +`categoryField` + `valueField` + `aggregate` on the component) is a +parse error since 16.0, not a silently ignored no-op. + | Property | Type | Description | |:--|:--|:--| | `type` | `string` | Standard component type or a custom string | diff --git a/content/docs/build/interface/pages.zh-Hans.mdx b/content/docs/build/interface/pages.zh-Hans.mdx index fbb7c51..fac24bb 100644 --- a/content/docs/build/interface/pages.zh-Hans.mdx +++ b/content/docs/build/interface/pages.zh-Hans.mdx @@ -13,7 +13,7 @@ const homePage = { regions: [ { name: 'header', width: 'full', components: [ { type: 'metric_card', id: 'total_revenue', label: 'Total Revenue', - properties: { object: 'opportunity', field: 'amount', aggregate: 'sum', format: 'currency' } }, + properties: { dataset: 'sales', values: ['revenue'], format: 'currency' } }, ]}, { name: 'main', width: 'large', components: [ { type: 'list_view', id: 'recent_deals', label: 'Recent Deals', @@ -71,13 +71,15 @@ regions: [ type: 'chart', id: 'revenue_chart', label: 'Revenue Trend', - properties: { chartType: 'line', object: 'opportunity', - categoryField: 'close_date', valueField: 'amount' }, + properties: { chartType: 'line', dataset: 'sales', + dimensions: ['close_month'], values: ['revenue'] }, events: { onClick: "navigate_to('opportunity_detail', { id: $event.id })" }, visibility: "os.user.profile == 'sales_manager'", } ``` +承载分析数据的组件(`chart`、`metric_card`)绑定一个具名**数据集**,并按名称选用其 `dimensions` + `values` —— 与[仪表盘](/docs/build/interface/dashboards)组件相同的 ADR-0021 形态,也是 Studio 唯一会生成的形态。已移除的内联分析形态(在组件上直接写 `object` + `categoryField` + `valueField` + `aggregate`)自 16.0 起是解析错误,而不再是被悄悄忽略的空操作。 + | 属性 | 类型 | 说明 | |:--|:--|:--| | `type` | `string` | 标准组件类型,或自定义字符串 | diff --git a/content/docs/configure/api-access.de.mdx b/content/docs/configure/api-access.de.mdx index f10997d..659581c 100644 --- a/content/docs/configure/api-access.de.mdx +++ b/content/docs/configure/api-access.de.mdx @@ -24,7 +24,8 @@ Wichtige Schnittstellen: | `/api/v1/auth/*` | Authentifizierung (Anmeldung, Sitzungen, OAuth, OIDC) | | `/api/v1/meta/*` | Metadaten-APIs (Objekte, Felder, Ansichten), sofern aktiviert | | `/api/v1/keys` | Erzeugt einen nur einmal angezeigten `sys_api_key` für den aufrufenden Benutzer (POST) | -| `/api/v1/mcp` | Model-Context-Protocol-Server über Streamable HTTP, wenn `OS_MCP_SERVER_ENABLED=true` | +| `/api/v1/batch` | Transaktionaler objektübergreifender Batch (POST) — ausschließlich atomar, jede Operation pro Objekt geprüft | +| `/api/v1/mcp` | Model-Context-Protocol-Server über Streamable HTTP — standardmäßig aktiv; deaktivierbar mit `OS_MCP_SERVER_ENABLED=false` | | `/api/v1/health` | Liveness-/Readiness-Probe (keine Authentifizierung) | Das Präfix lautet standardmäßig `/api/v1` (der API-`basePath` von `/api` plus diff --git a/content/docs/configure/api-access.es.mdx b/content/docs/configure/api-access.es.mdx index 069716f..0c1ae67 100644 --- a/content/docs/configure/api-access.es.mdx +++ b/content/docs/configure/api-access.es.mdx @@ -24,7 +24,8 @@ Superficies destacadas: | `/api/v1/auth/*` | Autenticación (inicio de sesión, sesiones, OAuth, OIDC) | | `/api/v1/meta/*` | APIs de metadatos (objetos, campos, vistas) cuando está habilitado | | `/api/v1/keys` | Genera un `sys_api_key` de un solo uso para el usuario que llama (POST) | -| `/api/v1/mcp` | Servidor Model Context Protocol sobre Streamable HTTP, cuando `OS_MCP_SERVER_ENABLED=true` | +| `/api/v1/batch` | Batch transaccional entre objetos (POST) — solo atómico, cada operación validada por objeto | +| `/api/v1/mcp` | Servidor Model Context Protocol sobre Streamable HTTP — activo de forma predeterminada; se desactiva con `OS_MCP_SERVER_ENABLED=false` | | `/api/v1/health` | Sonda de liveness/readiness (sin autenticación) | El prefijo es `/api/v1` de forma predeterminada (el `basePath` de la API, diff --git a/content/docs/configure/api-access.fr.mdx b/content/docs/configure/api-access.fr.mdx index 7889884..15e3902 100644 --- a/content/docs/configure/api-access.fr.mdx +++ b/content/docs/configure/api-access.fr.mdx @@ -24,7 +24,8 @@ Surfaces notables : | `/api/v1/auth/*` | Authentification (connexion, sessions, OAuth, OIDC) | | `/api/v1/meta/*` | API de métadonnées (objets, champs, vues) lorsqu'elles sont activées | | `/api/v1/keys` | Génère une `sys_api_key` affichée une seule fois pour l'utilisateur appelant (POST) | -| `/api/v1/mcp` | Serveur Model Context Protocol via Streamable HTTP, lorsque `OS_MCP_SERVER_ENABLED=true` | +| `/api/v1/batch` | Batch transactionnel inter-objets (POST) — atomique uniquement, chaque opération contrôlée par objet | +| `/api/v1/mcp` | Serveur Model Context Protocol via Streamable HTTP — actif par défaut ; se désactive avec `OS_MCP_SERVER_ENABLED=false` | | `/api/v1/health` | Sonde de vivacité/disponibilité (sans authentification) | Le préfixe est `/api/v1` par défaut (le `basePath` de l'API `/api` plus la diff --git a/content/docs/configure/api-access.ja.mdx b/content/docs/configure/api-access.ja.mdx index 7939134..bb01de5 100644 --- a/content/docs/configure/api-access.ja.mdx +++ b/content/docs/configure/api-access.ja.mdx @@ -23,7 +23,8 @@ https:///api/v1 | `/api/v1/auth/*` | 認証(サインイン、セッション、OAuth、OIDC) | | `/api/v1/meta/*` | 有効化されている場合のメタデータ API(オブジェクト、フィールド、ビュー) | | `/api/v1/keys` | 呼び出しユーザー向けに一度だけ表示される `sys_api_key` を発行(POST) | -| `/api/v1/mcp` | `OS_MCP_SERVER_ENABLED=true` のとき、Streamable HTTP 経由の Model Context Protocol サーバー | +| `/api/v1/batch` | オブジェクト横断のトランザクショナルバッチ(POST)— アトミックのみ、各操作をオブジェクト単位でゲート | +| `/api/v1/mcp` | Streamable HTTP 経由の Model Context Protocol サーバー — デフォルトで有効。`OS_MCP_SERVER_ENABLED=false` で無効化 | | `/api/v1/health` | 死活/準備状態のプローブ(認証不要) | プレフィックスのデフォルトは `/api/v1`(API の `basePath` である `/api` と diff --git a/content/docs/configure/api-access.ko.mdx b/content/docs/configure/api-access.ko.mdx index 12b0c25..ce1b5d1 100644 --- a/content/docs/configure/api-access.ko.mdx +++ b/content/docs/configure/api-access.ko.mdx @@ -23,7 +23,8 @@ https:///api/v1 | `/api/v1/auth/*` | 인증 (로그인, 세션, OAuth, OIDC) | | `/api/v1/meta/*` | 활성화된 경우 메타데이터 API (오브젝트, 필드, 뷰) | | `/api/v1/keys` | 호출 사용자를 위해 한 번만 표시되는 `sys_api_key` 발급 (POST) | -| `/api/v1/mcp` | `OS_MCP_SERVER_ENABLED=true`일 때 Streamable HTTP를 통한 Model Context Protocol 서버 | +| `/api/v1/batch` | 오브젝트 간 트랜잭션 배치 (POST) — 원자적 실행만 지원, 모든 작업을 오브젝트별로 게이트 | +| `/api/v1/mcp` | Streamable HTTP를 통한 Model Context Protocol 서버 — 기본 활성화, `OS_MCP_SERVER_ENABLED=false`로 비활성화 | | `/api/v1/health` | 활성/준비 상태 프로브 (인증 없음) | 접두사는 기본적으로 `/api/v1`(API `basePath`인 `/api`에 `version`인 `v1`을 diff --git a/content/docs/configure/api-access.mdx b/content/docs/configure/api-access.mdx index 03f25f3..4e02b66 100644 --- a/content/docs/configure/api-access.mdx +++ b/content/docs/configure/api-access.mdx @@ -23,7 +23,8 @@ Notable surfaces: | `/api/v1/auth/*` | Authentication (sign-in, sessions, OAuth, OIDC) | | `/api/v1/meta/*` | Metadata APIs (objects, fields, views) when enabled | | `/api/v1/keys` | Mint a show-once `sys_api_key` for the calling user (POST) | -| `/api/v1/mcp` | Model Context Protocol server over Streamable HTTP, when `OS_MCP_SERVER_ENABLED=true` | +| `/api/v1/batch` | Transactional cross-object batch (POST) — atomic-only, every operation gated per object | +| `/api/v1/mcp` | Model Context Protocol server over Streamable HTTP — on by default; disable with `OS_MCP_SERVER_ENABLED=false` | | `/api/v1/health` | Liveness/readiness probe (no auth) | The prefix defaults to `/api/v1` (the API `basePath` of `/api` plus the @@ -41,6 +42,14 @@ API exposure is governed per object in the artifact, not at the gateway: Both are enforced at the REST layer — see [REST API → Restricting the API surface](/docs/reference/rest-api#restricting-the-api-surface). +The same gates cover the transactional batch route (16.0): +`POST /api/v1/batch` validates its body, enforces `apiEnabled` / +`apiMethods` for **every** operation *before* opening the transaction, +requires ids for update/delete, and rejects unresolvable `{ $ref }` +parent references. The route is atomic-only — `atomic: false` is +rejected with `400 BATCH_NOT_ATOMIC`; non-atomic bulk stays on the +per-object `createMany` / `updateMany` endpoints. + ## Authentication options Callers can authenticate in four ways: @@ -151,3 +160,7 @@ when the corresponding capability is included in the image. Customer integrations should generate clients from the per-deployment OpenAPI document rather than hand-rolling URLs, because object names and generated routes follow the deployed artifact. + +Discovery also advertises `capabilities.transactionalBatch` (16.0), so +clients can detect the atomic batch route declaratively at connect time +instead of probing for `404`/`405`/`501`. diff --git a/content/docs/configure/api-access.zh-Hans.mdx b/content/docs/configure/api-access.zh-Hans.mdx index 2aabc8a..449fd70 100644 --- a/content/docs/configure/api-access.zh-Hans.mdx +++ b/content/docs/configure/api-access.zh-Hans.mdx @@ -21,7 +21,8 @@ https:///api/v1 | `/api/v1/auth/*` | 身份验证(登录、会话、OAuth、OIDC) | | `/api/v1/meta/*` | 元数据 API(对象、字段、视图),在启用时可用 | | `/api/v1/keys` | 为当前调用用户生成只显示一次的 `sys_api_key`(POST) | -| `/api/v1/mcp` | 当 `OS_MCP_SERVER_ENABLED=true` 时,通过 Streamable HTTP 提供的 Model Context Protocol 服务器 | +| `/api/v1/batch` | 跨对象事务批量写入(POST)——仅支持原子模式,每个操作都按对象门控 | +| `/api/v1/mcp` | 通过 Streamable HTTP 提供的 Model Context Protocol 服务器——默认开启;设置 `OS_MCP_SERVER_ENABLED=false` 可关闭 | | `/api/v1/health` | 存活/就绪探针(无需身份验证) | 前缀默认为 `/api/v1`(即 API 的 `basePath` `/api` 加上 `version` `v1`),并且可在 ObjectOS stack 上配置。 @@ -36,6 +37,8 @@ API 暴露在 artifact 中按对象逐个治理,而不是在网关层: 两者都在 REST 层强制执行 —— 参见 [REST API → 限制 API 暴露面](/docs/reference/rest-api#restricting-the-api-surface)。 +同样的闸门也覆盖事务批量路由(16.0):`POST /api/v1/batch` 会校验请求体,在开启事务*之前*就对**每个**操作强制执行 `apiEnabled` / `apiMethods`,update/delete 必须携带 id,无法解析的 `{ $ref }` 父引用会被拒绝。该路由只支持原子模式——`atomic: false` 会被以 `400 BATCH_NOT_ATOMIC` 拒绝;非原子的批量写入请继续使用按对象的 `createMany` / `updateMany` 端点。 + ## 身份验证方式 调用方可以通过四种方式进行身份验证: @@ -114,3 +117,5 @@ API 密钥仍作为 CI 与无头代理的并行通道——`x-api-key` 和 `Auth ## OpenAPI / 发现 当镜像中包含相应能力时,运行时可以为自动生成的 REST API 提供 OpenAPI 文档。客户的集成应基于每个部署的 OpenAPI 文档生成客户端,而不是手写 URL,因为对象名称和生成的路由会跟随已部署的 artifact 而变化。 + +发现(discovery)还会通告 `capabilities.transactionalBatch`(16.0),客户端可以在连接时以声明方式探知原子批量路由是否可用,而不必靠探测 `404`/`405`/`501`。 diff --git a/content/docs/configure/authentication.mdx b/content/docs/configure/authentication.mdx index b214ba4..a49c659 100644 --- a/content/docs/configure/authentication.mdx +++ b/content/docs/configure/authentication.mdx @@ -50,7 +50,14 @@ Platform admins can provision accounts without the email invite flow: `PASSWORD_EXPIRED` until changed). - `POST /api/v1/auth/admin/import-users` bulk-imports rows / CSV / XLSX (max 500 rows per request) with dry-run and upsert-by-email-or-phone - modes; existing users never get their passwords reset. + modes; existing users never get their passwords reset. Since 16.0 the + default `passwordPolicy` is `'auto'`: rows with a deliverable channel + (real email + wired email service, or phone + SMS invite path) are + **invited**, and only unreachable rows get a temporary password + (returned once, `must_change_password`). Callers that relied on the + old identity-only default must now pass `passwordPolicy: 'none'` + explicitly; per-row outcomes are on `rows[].delivery` with a + `summary.delivery` breakdown. ## Required secret diff --git a/content/docs/configure/authentication.zh-Hans.mdx b/content/docs/configure/authentication.zh-Hans.mdx index bdc72cc..fe306fe 100644 --- a/content/docs/configure/authentication.zh-Hans.mdx +++ b/content/docs/configure/authentication.zh-Hans.mdx @@ -35,7 +35,7 @@ ObjectOS 使用由 Better Auth 驱动的 ObjectStack 认证插件。认证是项 平台管理员可以不经邮件邀请流程直接开通账户: - `POST /api/v1/auth/admin/create-user` 直接创建用户,可选生成一次性密码(仅返回一次,不持久化)。`must_change_password` 标志强制首次登录时修改密码(未修改前返回 403 `PASSWORD_EXPIRED`)。 -- `POST /api/v1/auth/admin/import-users` 批量导入行 / CSV / XLSX(每次请求最多 500 行),支持 dry-run 和按邮箱或手机号 upsert;已存在用户的密码永不重置。 +- `POST /api/v1/auth/admin/import-users` 批量导入行 / CSV / XLSX(每次请求最多 500 行),支持 dry-run 和按邮箱或手机号 upsert;已存在用户的密码永不重置。自 16.0 起,默认 `passwordPolicy` 为 `'auto'`:具有可送达通道的行(真实邮箱 + 已接通的邮件服务,或手机号 + 短信邀请通道)会被**邀请**,只有无法触达的行才得到临时密码(仅返回一次,`must_change_password`)。依赖旧的仅建身份默认行为的调用方现在必须显式传入 `passwordPolicy: 'none'`;每行结果在 `rows[].delivery` 上,并附 `summary.delivery` 汇总。 ## 必需的 Secret diff --git a/content/docs/configure/mcp.mdx b/content/docs/configure/mcp.mdx index cf2c928..7b87c25 100644 --- a/content/docs/configure/mcp.mdx +++ b/content/docs/configure/mcp.mdx @@ -34,6 +34,10 @@ 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 ``` +> On a local dev server you don't even have to type this: since 16.0 the +> `os dev` startup banner prints the MCP endpoint, the agent-skill URL, +> and a ready-to-paste `claude mcp add` command. + For headless use (CI, containers) skip OAuth and attach an [API key](#headless-api-keys) instead: @@ -91,14 +95,35 @@ For a long-lived integration, bind the key to a dedicated service user with a minimal permission set — see [Service accounts & API keys](/docs/configure/users#service-accounts--api-keys). +## The stdio transport (16.0) + +Besides HTTP, the runtime can serve MCP over **stdio** for hosts that +spawn the server as a child process. Since 16.0 (ADR-0101) stdio has its +own switch and always runs under a real identity: + +- **`OS_MCP_STDIO_ENABLED=true`** turns stdio auto-start on (default + **off**). `OS_MCP_SERVER_ENABLED` now governs only the HTTP surface — + the legacy behavior where `OS_MCP_SERVER_ENABLED=true` also started + stdio still works for one release, with a deprecation warning. +- Stdio **requires `OS_MCP_STDIO_API_KEY=osk_...`**. The key is resolved + through the same verify + authorization chain as HTTP and REST, so + object permissions, record access, field-level security, and tenant + scoping apply to stdio calls exactly as on `/api/v1/mcp` — and the key + is re-resolved per read, so revoking it takes effect immediately. +- The transport is **fail-closed**: enabling auto-start without a + resolvable key refuses to boot, and there is deliberately no unscoped + fallback and no `system` bypass. For full authority, mint the key on a + platform-admin or dedicated service identity. + ## What the agent gets -Ten data and action tools, generated from your metadata: +Eleven data, action, and authoring tools, generated from your metadata: | Tool | What it does | |---|---| | `list_objects` / `describe_object` | Discover which objects exist and their fields | | `query_records` / `get_record` | Read data (list queries are capped at 50 rows per page by default) | +| `validate_expression` | Lint a formula against the real object schema *before* saving it (16.0): errors (bare field refs, unknown fields with did-you-mean), warnings (type-soundness, date-equality pitfalls), the in-scope field/function inventory, and the inferred result type. Read-only, `data:read`-scoped, fail-closed on `sys_*` | | `aggregate_records` | Grouped aggregation (registered when the active driver supports it) | | `create_record` / `update_record` / `delete_record` | Write data | | `list_actions` / `run_action` | Discover and invoke your business actions by name | @@ -157,6 +182,7 @@ fully operational. |---|---| | `404` on `/api/v1/mcp` | The surface is disabled — unset `OS_MCP_SERVER_ENABLED` (default is on) | | `501 Not Implemented` | The MCP plugin isn't part of this build — check your stack's plugins | +| Boot refuses with stdio enabled | Stdio is fail-closed (16.0) — set `OS_MCP_STDIO_API_KEY` to a resolvable `osk_` key, or turn `OS_MCP_STDIO_ENABLED` off | | `401` on every call | Anonymous or invalid credentials. Interactive clients: complete the browser login. Headless: check the `osk_` key and header spelling | | `403 insufficient_scope` | The OAuth token lacks the scope for that tool family (e.g. writes without `data:write`) — reconnect and grant the scope | | An action is missing from `list_actions` | `ai.exposed` is not `true`, `ai.description` is shorter than 40 characters, the type isn't headless-callable (`url` / `modal` / `form` never appear), it targets a `sys_*` object, or the caller fails its `requiredPermissions` | diff --git a/content/docs/configure/mcp.zh-Hans.mdx b/content/docs/configure/mcp.zh-Hans.mdx index f66fb3f..7aa3fb2 100644 --- a/content/docs/configure/mcp.zh-Hans.mdx +++ b/content/docs/configure/mcp.zh-Hans.mdx @@ -21,6 +21,8 @@ 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 ``` +> 在本地开发服务器上,这条命令甚至不用你亲手输入:自 16.0 起,`os dev` 的启动横幅会打印 MCP 端点、agent skill 的 URL,以及一条可直接复制粘贴的 `claude mcp add` 命令。 + 无头场景(CI、容器)跳过 OAuth,改为附加 [API Key](#headless-api-keys): ```bash @@ -69,14 +71,23 @@ curl -b cookies.txt -X POST https://your-deployment.example.com/api/v1/keys 对于长期集成,把 Key 绑定到带最小权限集的专用服务用户——参见[服务账号与 API Key](/docs/configure/users#service-accounts--api-keys)。 +## stdio 传输(16.0) + +除了 HTTP,运行时还可以通过 **stdio** 提供 MCP 服务,供以子进程方式拉起服务器的宿主使用。自 16.0(ADR-0101)起,stdio 拥有自己的开关,并且始终以真实身份运行: + +- **`OS_MCP_STDIO_ENABLED=true`** 开启 stdio 自动启动(默认**关闭**)。`OS_MCP_SERVER_ENABLED` 现在只管 HTTP 入口——`OS_MCP_SERVER_ENABLED=true` 同时拉起 stdio 的旧行为还会保留一个版本,并伴随弃用警告。 +- stdio **必须配置 `OS_MCP_STDIO_API_KEY=osk_...`**。该 Key 通过与 HTTP、REST 完全相同的验证 + 授权链解析,因此对象权限、记录访问、字段级安全和租户隔离对 stdio 调用的作用与 `/api/v1/mcp` 完全一致——并且每次读取都会重新解析 Key,吊销立即生效。 +- 该传输是 **fail-closed** 的:开启自动启动却没有可解析的 Key,启动会直接被拒绝;刻意不提供任何无身份回退,也没有 `system` 旁路。若需要完整权限,请在平台管理员或专用服务身份上签发 Key。 + ## Agent 能得到什么 -十个数据与操作工具,由你的元数据生成: +十一个数据、操作与创作工具,由你的元数据生成: | 工具 | 用途 | |---|---| | `list_objects` / `describe_object` | 发现有哪些对象及其字段 | | `query_records` / `get_record` | 读取数据(列表查询默认每页上限 50 行) | +| `validate_expression` | 在保存*之前*对照真实对象 schema 校验公式(16.0):错误(裸字段引用、未知字段并给出 did-you-mean)、警告(类型健全性、日期相等陷阱)、作用域内的字段/函数清单,以及推断的结果类型。只读、`data:read` scope、对 `sys_*` fail-closed | | `aggregate_records` | 分组聚合(当前驱动支持时才注册) | | `create_record` / `update_record` / `delete_record` | 写入数据 | | `list_actions` / `run_action` | 按名称发现并调用你的业务操作 | @@ -111,6 +122,7 @@ What objects does this app have, and what fields does the main one carry? |---|---| | `/api/v1/mcp` 返回 `404` | 入口被禁用——取消设置 `OS_MCP_SERVER_ENABLED`(默认开启) | | `501 Not Implemented` | 此构建不包含 MCP 插件——检查你的栈的插件配置 | +| 开启 stdio 后启动被拒绝 | stdio 是 fail-closed 的(16.0)——把 `OS_MCP_STDIO_API_KEY` 设为可解析的 `osk_` Key,或关闭 `OS_MCP_STDIO_ENABLED` | | 每次调用都 `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` | diff --git a/content/docs/configure/users.mdx b/content/docs/configure/users.mdx index 3832b06..a34ee70 100644 --- a/content/docs/configure/users.mdx +++ b/content/docs/configure/users.mdx @@ -60,7 +60,7 @@ first-class: |---|---|---| | **Invite User** (toolbar) | The person has a reachable email | Sends an invitation; they set their own password on accept | | **Create User** (toolbar) | No email flow wanted — or phone-only staff | Creates a sign-in-ready account (email and/or phone); a generated temporary password is shown **once**, with password change forced on first login | -| **Import** (toolbar) | Bulk onboarding from CSV/Excel | Wizard with column mapping and dry-run preview; up to 500 rows per batch. Pick the sign-in policy: no password (first sign-in via OTP/magic/reset link), send invitations, or one-time temporary passwords | +| **Import** (toolbar) | Bulk onboarding from CSV/Excel | Wizard with column mapping and dry-run preview; up to 500 rows per batch. Pick the sign-in policy — the default **auto** (16.0) invites every reachable row and falls back to a one-time temporary password only for unreachable ones; or choose explicitly: no password (first sign-in via OTP/magic/reset link), send invitations, or one-time temporary passwords | Pending invitations are listed under **Setup → People & Organization → Invitations**. @@ -76,6 +76,14 @@ curl -X POST https://crm.example.com/api/v1/auth/admin/create-user curl -X POST https://crm.example.com/api/v1/auth/admin/import-users ``` +Since 16.0 the import default is `passwordPolicy: 'auto'`: rows with a +deliverable channel (a real email plus a wired email service, or a phone +number with the SMS invite path) are **invited**; only unreachable rows +get a temporary password (returned once, `must_change_password`). Pass +`passwordPolicy: 'none'` explicitly for the old identity-only behavior; +per-row outcomes surface on `rows[].delivery`, with a `summary.delivery` +breakdown. + Payloads, password policies, and phone-only accounts are covered in [Authentication → Admin user management](/docs/configure/authentication#admin-user-management-objectstack-143). diff --git a/content/docs/configure/users.zh-Hans.mdx b/content/docs/configure/users.zh-Hans.mdx index f876b2e..f73a45c 100644 --- a/content/docs/configure/users.zh-Hans.mdx +++ b/content/docs/configure/users.zh-Hans.mdx @@ -37,7 +37,7 @@ description: 搭建业务单元树、添加和邀请用户、管理成员资格 |---|---|---| | **Invite User**(邀请用户,工具栏) | 此人有可达的邮箱 | 发送邀请;对方接受时自行设置密码 | | **Create User**(创建用户,工具栏) | 不想走邮件流程——或只有手机号的员工 | 创建可直接登录的账户(邮箱和/或手机号);生成的临时密码只显示**一次**,并强制首次登录时修改密码 | -| **Import**(导入,工具栏) | 从 CSV/Excel 批量入职 | 带列映射和 dry-run 预览的向导;每批最多 500 行。可选择登录策略:无密码(首次通过 OTP/魔法链接/重置链接登录)、发送邀请,或一次性临时密码 | +| **Import**(导入,工具栏) | 从 CSV/Excel 批量入职 | 带列映射和 dry-run 预览的向导;每批最多 500 行。可选择登录策略——默认的 **auto**(16.0)会邀请所有可送达的行,仅对无法送达的行回退到一次性临时密码;也可以显式选择:无密码(首次通过 OTP/魔法链接/重置链接登录)、发送邀请,或一次性临时密码 | 待接受的邀请列在 **Setup → People & Organization → Invitations**(邀请)下。 @@ -51,6 +51,8 @@ curl -X POST https://crm.example.com/api/v1/auth/admin/create-user curl -X POST https://crm.example.com/api/v1/auth/admin/import-users ``` +自 16.0 起,导入的默认策略是 `passwordPolicy: 'auto'`:具有可送达通道的行(真实邮箱且已接通邮件服务,或手机号且具备短信邀请通道)会被**邀请**;只有确实无法触达的行才会得到临时密码(仅返回一次,`must_change_password`)。想要旧的仅建身份行为,须显式传入 `passwordPolicy: 'none'`;每行结果在 `rows[].delivery` 上给出,并附 `summary.delivery` 汇总。 + 请求体、密码策略和仅手机号账户的细节,参见[认证 → 管理员用户管理](/docs/configure/authentication#admin-user-management-objectstack-143)。 ## 把人放进组织树(成员资格) diff --git a/content/docs/reference/environment-variables.de.mdx b/content/docs/reference/environment-variables.de.mdx index 014223c..244009d 100644 --- a/content/docs/reference/environment-variables.de.mdx +++ b/content/docs/reference/environment-variables.de.mdx @@ -28,7 +28,9 @@ Verwenden Sie Systemeinstellungen für anwendungsbezogene Konfiguration, die von | `OS_PROJECT_ID` | Optional | Veralteter Alias für `OS_ENVIRONMENT_ID`, der von der ObjectOS-Konfiguration aus Gründen der Abwärtskompatibilität akzeptiert wird. Bevorzugen Sie `OS_ENVIRONMENT_ID` in neuen Deployments. | | `OS_ENVIRONMENT_ID` | Optional | Umgebungs-ID für den Standalone-Stack (Standard `proj_local`). Wird auch zur Ableitung des projektspezifischen Auth-Geheimnisses verwendet. Die ObjectOS-Konfiguration akzeptiert außerdem den veralteten Alias `OS_PROJECT_ID`. | | `OS_ORGANIZATION_ID` | Optional | Standard-Organisations-ID für den dateibasierten Modus (Standard `org_local`). | -| `OS_MCP_SERVER_ENABLED` | Nein | Auf `true` setzen, um den Model-Context-Protocol-Server über Streamable HTTP unter `/api/v1/mcp` bereitzustellen (8.0+). Standardmäßig aus — der Endpunkt gibt `404` zurück, bis er aktiviert ist, und erfordert dann einen authentifizierten Principal. | +| `OS_MCP_SERVER_ENABLED` | Nein | Steuert den Model-Context-Protocol-Server über Streamable HTTP unter `/api/v1/mcp` — und nur diese HTTP-Oberfläche. **Standardmäßig an** (16.0+): ungesetzt wird der Endpunkt bereitgestellt; ein explizit falscher Wert (`false`/`0`/`off`/`no`) deaktiviert ihn. Erfordert einen authentifizierten Principal. Legacy: `OS_MCP_SERVER_ENABLED=true` startet für ein weiteres Release zusätzlich den stdio-Transport (mit Deprecation-Warnung) — stattdessen `OS_MCP_STDIO_ENABLED` verwenden. | +| `OS_MCP_STDIO_ENABLED` | Nein | Auf einen wahren Wert (`true`/`1`/`on`/`yes`) setzen, um den langlebigen **stdio**-MCP-Transport automatisch zu starten. Standardmäßig aus und getrennt von `OS_MCP_SERVER_ENABLED` (das nur die HTTP-Oberfläche steuert). Erfordert `OS_MCP_STDIO_API_KEY`. | +| `OS_MCP_STDIO_API_KEY` | Wenn stdio aktiviert ist | API-Schlüssel (`osk_...`), an dessen Principal der stdio-Transport gebunden wird. Wird über dieselbe Verify-/Autorisierungskette wie HTTP-MCP aufgelöst, RLS/FLS und Tenant-Scoping gelten. Fail-closed: Ist stdio-Autostart aktiv und kein auflösbarer Schlüssel vorhanden, verweigert der Server den Start des stdio-Transports. | | `OS_CLOUD_URL` | Optional | Basis-URL der Steuerungsebene für den Marketplace-Proxy und die lokale Paketinstallation. Auf `off` oder `local` setzen, um Marketplace-Funktionen zu deaktivieren. Wird in der Standalone-Distribution nicht mehr für das Hostname-Routing des Host-Stacks verwendet. | | `OS_MULTI_ORG_ENABLED` | Nein | Auf `true` setzen, um Multi-Mandanten-Routing / Organisationswechsel zu aktivieren (Standard `false`). Veralteter Alias: `OS_MULTI_TENANT`. | | `OS_RUNTIME_PORT` | Nur Dev | Localhost-Port, der zum Aufbau von Plattform-SSO-Callback-URLs bei der Entwicklung auf `http://localhost:` verwendet wird. | diff --git a/content/docs/reference/environment-variables.es.mdx b/content/docs/reference/environment-variables.es.mdx index b0e7536..56b7fc3 100644 --- a/content/docs/reference/environment-variables.es.mdx +++ b/content/docs/reference/environment-variables.es.mdx @@ -28,7 +28,9 @@ Use la configuración del sistema para la configuración de la aplicación edita | `OS_PROJECT_ID` | Opcional | Alias heredado de `OS_ENVIRONMENT_ID`, aceptado por la configuración de ObjectOS por compatibilidad con versiones anteriores. Prefiera `OS_ENVIRONMENT_ID` en los nuevos despliegues. | | `OS_ENVIRONMENT_ID` | Opcional | Id de entorno para el stack independiente (por defecto `proj_local`). También se usa para derivar el secreto de autenticación por proyecto. La configuración de ObjectOS también acepta el alias heredado `OS_PROJECT_ID`. | | `OS_ORGANIZATION_ID` | Opcional | Id de organización por defecto para el modo respaldado por archivo (por defecto `org_local`). | -| `OS_MCP_SERVER_ENABLED` | No | Establézcalo en `true` para exponer el servidor Model Context Protocol sobre Streamable HTTP en `/api/v1/mcp` (8.0+). Desactivado por defecto: el endpoint devuelve `404` hasta que se habilita y, una vez activo, requiere un principal autenticado. | +| `OS_MCP_SERVER_ENABLED` | No | Gobierna el servidor Model Context Protocol sobre Streamable HTTP en `/api/v1/mcp` — y solo esa superficie HTTP. **Activado por defecto** (16.0+): sin definir, el endpoint se sirve; establezca un valor falso explícito (`false`/`0`/`off`/`no`) para desactivarlo. Requiere un principal autenticado. Legado: `OS_MCP_SERVER_ENABLED=true` también arranca el transporte stdio durante una versión más (con aviso de obsolescencia) — use `OS_MCP_STDIO_ENABLED` en su lugar. | +| `OS_MCP_STDIO_ENABLED` | No | Establézcalo en un valor verdadero (`true`/`1`/`on`/`yes`) para arrancar automáticamente el transporte MCP **stdio** de larga duración. Desactivado por defecto y separado de `OS_MCP_SERVER_ENABLED` (que gobierna solo la superficie HTTP). Requiere `OS_MCP_STDIO_API_KEY`. | +| `OS_MCP_STDIO_API_KEY` | Cuando stdio está habilitado | Clave de API (`osk_...`) a cuyo principal se vincula el transporte stdio. Se resuelve por la misma cadena de verificación y autorización que HTTP MCP, por lo que se aplican RLS/FLS y el alcance de tenant. Falla cerrado: con el autoarranque de stdio habilitado y sin una clave resoluble, el servidor se niega a iniciar el transporte stdio. | | `OS_CLOUD_URL` | Opcional | URL base del plano de control para el proxy del marketplace y la instalación local de paquetes. Establézcalo en `off` o `local` para deshabilitar las funciones del marketplace. Ya no se usa para el enrutamiento por nombre de host del host-stack en la distribución independiente. | | `OS_MULTI_ORG_ENABLED` | No | Establézcalo en `true` para habilitar el enrutamiento multiinquilino / cambio de organización (por defecto `false`). Alias heredado: `OS_MULTI_TENANT`. | | `OS_RUNTIME_PORT` | Solo desarrollo | Puerto de localhost utilizado para construir las URL de callback de SSO de plataforma al desarrollar en `http://localhost:`. | diff --git a/content/docs/reference/environment-variables.fr.mdx b/content/docs/reference/environment-variables.fr.mdx index c836740..e0bd001 100644 --- a/content/docs/reference/environment-variables.fr.mdx +++ b/content/docs/reference/environment-variables.fr.mdx @@ -27,7 +27,9 @@ Utilisez les paramètres système pour la configuration applicative modifiable p | `OS_PROJECT_ID` | Optionnel | Alias hérité pour `OS_ENVIRONMENT_ID`, accepté par la configuration ObjectOS pour des raisons de rétrocompatibilité. Préférez `OS_ENVIRONMENT_ID` dans les nouveaux déploiements. | | `OS_ENVIRONMENT_ID` | Optionnel | Identifiant d'environnement pour le stack autonome (par défaut `proj_local`). Également utilisé pour dériver le secret d'authentification par projet. La configuration ObjectOS accepte aussi l'alias hérité `OS_PROJECT_ID`. | | `OS_ORGANIZATION_ID` | Optionnel | Identifiant d'organisation par défaut pour le mode adossé à un fichier (par défaut `org_local`). | -| `OS_MCP_SERVER_ENABLED` | Non | Définissez-le à `true` pour exposer le serveur Model Context Protocol via Streamable HTTP sur `/api/v1/mcp` (8.0+). Désactivé par défaut : l'endpoint renvoie `404` jusqu'à activation, puis requiert un principal authentifié. | +| `OS_MCP_SERVER_ENABLED` | Non | Gouverne le serveur Model Context Protocol via Streamable HTTP sur `/api/v1/mcp` — et uniquement cette surface HTTP. **Activé par défaut** (16.0+) : non défini, l'endpoint est servi ; définissez une valeur explicitement fausse (`false`/`0`/`off`/`no`) pour le désactiver. Requiert un principal authentifié. Héritage : `OS_MCP_SERVER_ENABLED=true` démarre aussi le transport stdio pendant encore une version (avec avertissement de dépréciation) — utilisez plutôt `OS_MCP_STDIO_ENABLED`. | +| `OS_MCP_STDIO_ENABLED` | Non | Définissez une valeur vraie (`true`/`1`/`on`/`yes`) pour démarrer automatiquement le transport MCP **stdio** de longue durée. Désactivé par défaut et distinct de `OS_MCP_SERVER_ENABLED` (qui ne gouverne que la surface HTTP). Requiert `OS_MCP_STDIO_API_KEY`. | +| `OS_MCP_STDIO_API_KEY` | Quand stdio est activé | Clé d'API (`osk_...`) au principal de laquelle le transport stdio est lié. Résolue via la même chaîne de vérification et d'autorisation que MCP HTTP : RLS/FLS et le périmètre de tenant s'appliquent. Fail-closed : si le démarrage automatique stdio est activé sans clé résoluble, le serveur refuse de démarrer le transport stdio. | | `OS_CLOUD_URL` | Optionnel | URL de base du plan de contrôle pour le proxy du marketplace et l'installation locale de paquets. Définissez-le à `off` ou `local` pour désactiver les fonctionnalités du marketplace. N'est plus utilisé pour le routage par nom d'hôte du host-stack dans la distribution autonome. | | `OS_MULTI_ORG_ENABLED` | Non | Définissez-le à `true` pour activer le routage multi-locataire / le changement d'organisation (par défaut `false`). Alias hérité : `OS_MULTI_TENANT`. | | `OS_RUNTIME_PORT` | Dev uniquement | Port localhost utilisé pour construire les URL de rappel SSO de la plateforme lors du développement sur `http://localhost:`. | diff --git a/content/docs/reference/environment-variables.ja.mdx b/content/docs/reference/environment-variables.ja.mdx index 36d8456..977e1af 100644 --- a/content/docs/reference/environment-variables.ja.mdx +++ b/content/docs/reference/environment-variables.ja.mdx @@ -27,7 +27,9 @@ description: ObjectOS ランタイムの環境変数リファレンス。 | `OS_PROJECT_ID` | 任意 | `OS_ENVIRONMENT_ID` のレガシーエイリアスで、後方互換性のために ObjectOS の設定が受け付けます。新しいデプロイメントでは `OS_ENVIRONMENT_ID` を優先してください。 | | `OS_ENVIRONMENT_ID` | 任意 | standalone stack の環境 id(デフォルト `proj_local`)。プロジェクトごとの認証シークレットの導出にも使用されます。ObjectOS の設定はレガシーエイリアス `OS_PROJECT_ID` も受け付けます。 | | `OS_ORGANIZATION_ID` | 任意 | ファイルバックモードのデフォルト組織 id(デフォルト `org_local`)。 | -| `OS_MCP_SERVER_ENABLED` | いいえ | `true` に設定すると、Model Context Protocol サーバーを Streamable HTTP 経由で `/api/v1/mcp` に公開します(8.0+)。デフォルトはオフ——有効化するまでエンドポイントは `404` を返し、有効化後は認証済みプリンシパルが必要です。 | +| `OS_MCP_SERVER_ENABLED` | いいえ | Streamable HTTP の `/api/v1/mcp` で提供される Model Context Protocol サーバー——この HTTP サーフェスのみ——を制御します。**デフォルトはオン**(16.0+):未設定ならエンドポイントは提供され、明示的な偽値(`false`/`0`/`off`/`no`)で無効化します。認証済みプリンシパルが必要です。レガシー:`OS_MCP_SERVER_ENABLED=true` はあと 1 リリースの間、非推奨警告付きで stdio トランスポートも自動起動します——代わりに `OS_MCP_STDIO_ENABLED` を使用してください。 | +| `OS_MCP_STDIO_ENABLED` | いいえ | 真値(`true`/`1`/`on`/`yes`)に設定すると、長寿命の **stdio** MCP トランスポートを自動起動します。デフォルトはオフで、`OS_MCP_SERVER_ENABLED`(HTTP サーフェスのみを制御)とは別のスイッチです。`OS_MCP_STDIO_API_KEY` が必要です。 | +| `OS_MCP_STDIO_API_KEY` | stdio 有効時 | stdio トランスポートをプリンシパルに束縛する API キー(`osk_...`)。HTTP MCP と同じ検証・認可チェーンで解決されるため、RLS/FLS とテナントスコープが適用されます。フェイルクローズド:stdio 自動起動が有効で解決可能なキーがない場合、サーバーは stdio トランスポートの起動を拒否します。 | | `OS_CLOUD_URL` | 任意 | marketplace プロキシとローカルパッケージインストールのためのコントロールプレーンのベース URL。`off` または `local` に設定すると marketplace 機能を無効化できます。standalone ディストリビューションでは host stack のホスト名ルーティングには使用されなくなりました。 | | `OS_MULTI_ORG_ENABLED` | いいえ | マルチテナントルーティング/組織切り替えを有効にするには `true` に設定します(デフォルト `false`)。レガシーエイリアス: `OS_MULTI_TENANT`。 | | `OS_RUNTIME_PORT` | 開発のみ | `http://localhost:` で開発する際にプラットフォーム SSO のコールバック URL を構築するために使用するローカルホストポート。 | diff --git a/content/docs/reference/environment-variables.ko.mdx b/content/docs/reference/environment-variables.ko.mdx index 1330004..779b39c 100644 --- a/content/docs/reference/environment-variables.ko.mdx +++ b/content/docs/reference/environment-variables.ko.mdx @@ -27,7 +27,9 @@ description: ObjectOS 런타임 환경 변수 참조. | `OS_PROJECT_ID` | 선택 | `OS_ENVIRONMENT_ID`의 레거시 별칭으로, 하위 호환성을 위해 ObjectOS 구성에서 허용됩니다. 새 배포에서는 `OS_ENVIRONMENT_ID`를 사용하는 것이 좋습니다. | | `OS_ENVIRONMENT_ID` | 선택 | standalone stack의 환경 id(기본값 `proj_local`). 프로젝트별 인증 시크릿을 파생하는 데에도 사용됩니다. ObjectOS 구성은 레거시 별칭 `OS_PROJECT_ID`도 허용합니다. | | `OS_ORGANIZATION_ID` | 선택 | 파일 기반 모드의 기본 조직 id(기본값 `org_local`). | -| `OS_MCP_SERVER_ENABLED` | 아니오 | `true`로 설정하면 Model Context Protocol 서버를 Streamable HTTP로 `/api/v1/mcp`에 노출합니다(8.0+). 기본값은 꺼짐 — 활성화하기 전에는 엔드포인트가 `404`를 반환하며, 활성화 후에는 인증된 주체가 필요합니다. | +| `OS_MCP_SERVER_ENABLED` | 아니오 | Streamable HTTP의 `/api/v1/mcp`로 제공되는 Model Context Protocol 서버 — 오직 이 HTTP 표면만 — 를 제어합니다. **기본값은 켜짐**(16.0+): 설정하지 않으면 엔드포인트가 제공되며, 명시적인 거짓 값(`false`/`0`/`off`/`no`)으로 비활성화합니다. 인증된 주체가 필요합니다. 레거시: `OS_MCP_SERVER_ENABLED=true`는 한 릴리스 동안 사용 중단 경고와 함께 stdio 전송도 자동 시작합니다 — 대신 `OS_MCP_STDIO_ENABLED`를 사용하세요. | +| `OS_MCP_STDIO_ENABLED` | 아니오 | 참 값(`true`/`1`/`on`/`yes`)으로 설정하면 장수명 **stdio** MCP 전송을 자동 시작합니다. 기본값은 꺼짐이며 `OS_MCP_SERVER_ENABLED`(HTTP 표면만 제어)와는 별개의 스위치입니다. `OS_MCP_STDIO_API_KEY`가 필요합니다. | +| `OS_MCP_STDIO_API_KEY` | stdio 활성화 시 | stdio 전송이 주체에 바인딩되는 API 키(`osk_...`). HTTP MCP와 동일한 검증·인가 체인으로 해석되므로 RLS/FLS와 테넌트 범위가 적용됩니다. 실패 시 닫힘(fail-closed): stdio 자동 시작이 활성화되었는데 해석 가능한 키가 없으면 서버는 stdio 전송 시작을 거부합니다. | | `OS_CLOUD_URL` | 선택 | marketplace 프록시 및 로컬 패키지 설치를 위한 컨트롤 플레인 기본 URL. `off` 또는 `local`로 설정하면 marketplace 기능을 비활성화합니다. standalone 배포판에서는 더 이상 host stack의 호스트명 라우팅에 사용되지 않습니다. | | `OS_MULTI_ORG_ENABLED` | 아니오 | 멀티 테넌트 라우팅/조직 전환을 활성화하려면 `true`로 설정합니다(기본값 `false`). 레거시 별칭: `OS_MULTI_TENANT`. | | `OS_RUNTIME_PORT` | 개발 전용 | `http://localhost:`에서 개발할 때 플랫폼 SSO 콜백 URL을 구성하는 데 사용되는 localhost 포트. | diff --git a/content/docs/reference/environment-variables.mdx b/content/docs/reference/environment-variables.mdx index 4372f3a..f0a3b57 100644 --- a/content/docs/reference/environment-variables.mdx +++ b/content/docs/reference/environment-variables.mdx @@ -27,7 +27,9 @@ Use system settings for tenant/user-editable application configuration. | `OS_PROJECT_ID` | Optional | Legacy alias for `OS_ENVIRONMENT_ID`, accepted by the ObjectOS config for backward compatibility. Prefer `OS_ENVIRONMENT_ID` in new deployments. | | `OS_ENVIRONMENT_ID` | Optional | Environment id for the standalone stack (default `proj_local`). Also used to derive the per-project auth secret. The ObjectOS config also accepts the legacy alias `OS_PROJECT_ID`. | | `OS_ORGANIZATION_ID` | Optional | Default organization id for file-backed mode (default `org_local`). | -| `OS_MCP_SERVER_ENABLED` | No | Set to `true` to expose the Model Context Protocol server over Streamable HTTP at `/api/v1/mcp` (8.0+). Off by default — the endpoint returns `404` until enabled, and requires an authenticated principal once on. | +| `OS_MCP_SERVER_ENABLED` | No | Governs the Model Context Protocol server over Streamable HTTP at `/api/v1/mcp` — and only that HTTP surface. **On by default** (16.0+): unset means the endpoint is served; set an explicit falsy value (`false`/`0`/`off`/`no`) to disable it. Requires an authenticated principal. Legacy: `OS_MCP_SERVER_ENABLED=true` also auto-starts the stdio transport for one more release with a deprecation warning — use `OS_MCP_STDIO_ENABLED` instead. | +| `OS_MCP_STDIO_ENABLED` | No | Set to a truthy value (`true`/`1`/`on`/`yes`) to auto-start the long-lived **stdio** MCP transport. Off by default and separate from `OS_MCP_SERVER_ENABLED` (which governs only the HTTP surface). Requires `OS_MCP_STDIO_API_KEY`. | +| `OS_MCP_STDIO_API_KEY` | When stdio is enabled | API key (`osk_...`) the stdio transport is principal-bound to. Resolved through the same verify + authorization chain as HTTP MCP, so RLS/FLS and tenant scoping apply. Fails closed: with stdio auto-start enabled and no resolvable key, the server refuses to start the stdio transport. | | `OS_CLOUD_URL` | Optional | Control-plane base URL for the marketplace proxy and local package install. Set to `off` or `local` to disable marketplace features. No longer used for host-stack hostname routing in the standalone distribution. | | `OS_MULTI_ORG_ENABLED` | No | Set to `true` to enable multi-tenant routing / organization switching (default `false`). Legacy alias: `OS_MULTI_TENANT`. | | `OS_RUNTIME_PORT` | Dev only | Localhost port used to build platform-SSO callback URLs when developing on `http://localhost:`. | diff --git a/content/docs/reference/environment-variables.zh-Hans.mdx b/content/docs/reference/environment-variables.zh-Hans.mdx index 5d109cb..705c84f 100644 --- a/content/docs/reference/environment-variables.zh-Hans.mdx +++ b/content/docs/reference/environment-variables.zh-Hans.mdx @@ -24,7 +24,9 @@ description: ObjectOS 运行时环境变量参考。 | `OS_PROJECT_ID` | 可选 | `OS_ENVIRONMENT_ID` 的旧别名,ObjectOS 配置为向后兼容接受。新部署中优先使用 `OS_ENVIRONMENT_ID`。 | | `OS_ENVIRONMENT_ID` | 可选 | standalone stack 的环境 id(默认 `proj_local`)。也用于派生项目级 auth 密钥。ObjectOS 配置也接受旧别名 `OS_PROJECT_ID`。 | | `OS_ORGANIZATION_ID` | 可选 | 文件支持模式下的默认组织 id(默认 `org_local`)。 | -| `OS_MCP_SERVER_ENABLED` | 否 | 设为 `true` 以通过 Streamable HTTP 在 `/api/v1/mcp` 暴露 Model Context Protocol 服务器(8.0+)。默认关闭——开启前端点返回 `404`,开启后需要已认证的主体。 | +| `OS_MCP_SERVER_ENABLED` | 否 | 控制通过 Streamable HTTP 在 `/api/v1/mcp` 提供的 Model Context Protocol 服务器——且仅控制该 HTTP 表面。**默认开启**(16.0+):未设置时端点即被提供;设置显式假值(`false`/`0`/`off`/`no`)可将其关闭。需要已认证的主体。遗留行为:`OS_MCP_SERVER_ENABLED=true` 在接下来一个版本内仍会附带弃用警告地自动启动 stdio 传输——请改用 `OS_MCP_STDIO_ENABLED`。 | +| `OS_MCP_STDIO_ENABLED` | 否 | 设为真值(`true`/`1`/`on`/`yes`)以自动启动长驻的 **stdio** MCP 传输。默认关闭,且与 `OS_MCP_SERVER_ENABLED`(仅控制 HTTP 表面)相互独立。需要 `OS_MCP_STDIO_API_KEY`。 | +| `OS_MCP_STDIO_API_KEY` | 启用 stdio 时必需 | stdio 传输绑定到的 API 密钥(`osk_...`)对应的主体。通过与 HTTP MCP 相同的校验 + 授权链解析,因此 RLS/FLS 与租户隔离同样生效。失败即关闭(fail-closed):启用 stdio 自动启动但没有可解析的密钥时,服务器会拒绝启动 stdio 传输。 | | `OS_CLOUD_URL` | 可选 | marketplace 代理与本地 package 安装的控制平面基础 URL。设为 `off` 或 `local` 可禁用 marketplace 功能。在 standalone 分发中不再用于 host stack 的主机名路由。 | | `OS_MULTI_ORG_ENABLED` | 否 | 设为 `true` 以启用多租户路由/组织切换(默认 `false`)。旧别名:`OS_MULTI_TENANT`。 | | `OS_RUNTIME_PORT` | 仅开发 | 当在 `http://localhost:` 开发时,用于构建平台 SSO 回调 URL 的本地端口。 | diff --git a/content/docs/reference/field-types.de.mdx b/content/docs/reference/field-types.de.mdx index d4cb1f6..e7cea5a 100644 --- a/content/docs/reference/field-types.de.mdx +++ b/content/docs/reference/field-types.de.mdx @@ -18,11 +18,9 @@ description: Jeder Feldtyp, den du auf einem Objekt deklarieren kannst — was e | `searchable` | boolean | `false` | Indexiert für `/api/v1/search` | | `multiple` | boolean | `false` | Speichert ein Array von Werten | | `defaultValue` | unknown | — | Anfangswert (Literal oder CEL) | -| `columnName` | string | = `name` | Überschreibt physische DB-Spalte | | `hidden` | boolean | `false` | In Standard-Console-Ansichten ausblenden | | `readonly` | boolean | `false` | In Formularen deaktivieren | | `system` | boolean | `false` | Automatisch eingefügt (id, created_at, …) | -| `index` | boolean | `false` | Erstellt einen DB-Index | | `externalId` | boolean | `false` | Für Upsert über externen Schlüssel geeignet | | `inlineHelpText` | string | — | Tooltip / Hilfetext | | `conditionalRequired` | `P`-Prädikat | — | Erforderlich, wenn CEL wahr ist | @@ -102,7 +100,9 @@ Häufige Optionen: { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` @@ -112,7 +112,7 @@ Häufige Optionen: | Typ | Funktion | Wichtige Option | |:--|:--|:--| | `formula` | Abgeleiteter Wert, beim Lesen oder Neuberechnen ausgewertet | `expression: F\`record.qty * record.unit_price\`` — siehe [CEL](./cel) | -| `summary` | Aggregation über eine untergeordnete Beziehung | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | Aggregation über eine untergeordnete Beziehung | `summaryOperations: { object, field, function, filter? }` (`count` \| `sum` \| `avg` \| `min` \| `max`; optionaler `filter` — eine Query-`where`-Bedingung, z. B. `{ status: 'received' }` — aggregiert nur passende Kind-Zeilen, 16.0+) | | `autonumber` | Automatisch hochzählende Anzeigenummer | `format` (z. B. `TKT-{0000}`), `startAt` | Formel-Beispiel: @@ -129,19 +129,18 @@ Formel-Beispiel: | Typ | Verwendung für | Wichtige Option | |:--|:--|:--| -| `image` | Bildanhänge | `fileAttachmentConfig` (siehe unten) | +| `image` | Bildanhänge | `multiple` / `accept` / `maxSize` (siehe unten) | | `file` | beliebige Datei | dasselbe | | `avatar` | Profilbilder | quadratischer Zuschnitt, sinnvolle Standardwerte | | `video` | Video-Uploads | Dauer + Thumbnail-Erfassung | | `audio` | Audio | Wellenform-Vorschau | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` @@ -167,7 +166,7 @@ fileAttachmentConfig: { | `qrcode` | rendert den Wert als QR-Code oder Barcode (EAN / UPC / Code128) | | `progress` | abgeleiteter Prozentwert als Balken dargestellt | | `tags` | frei wählbares Tag-Array mit Autovervollständigung | -| `vector` | Embedding-Spalte — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | Embedding-Spalte — flaches `dimensions` (z. B. `1536` für OpenAI-Embeddings); das verschachtelte `vectorConfig` (`distanceMetric`/`indexed`/`indexType`) wurde in 16.0 entfernt | ## Systemfelder (automatisch auf jedem Objekt eingefügt) diff --git a/content/docs/reference/field-types.es.mdx b/content/docs/reference/field-types.es.mdx index 11d4fd3..3839e78 100644 --- a/content/docs/reference/field-types.es.mdx +++ b/content/docs/reference/field-types.es.mdx @@ -18,11 +18,9 @@ description: Cada tipo de campo que puedes declarar en un objeto — qué almace | `searchable` | boolean | `false` | Indexado para `/api/v1/search` | | `multiple` | boolean | `false` | Almacena un arreglo de valores | | `defaultValue` | unknown | — | Valor inicial (literal o CEL) | -| `columnName` | string | = `name` | Sobrescribe la columna física de la BD | | `hidden` | boolean | `false` | Oculta de las vistas predeterminadas de Console | | `readonly` | boolean | `false` | Deshabilita en formularios | | `system` | boolean | `false` | Inyectado automáticamente (id, created_at, …) | -| `index` | boolean | `false` | Crea un índice en la BD | | `externalId` | boolean | `false` | Apto para upsert mediante clave externa | | `inlineHelpText` | string | — | Texto de información sobre herramientas / ayuda | | `conditionalRequired` | predicado `P` | — | Obligatorio cuando el CEL es verdadero | @@ -102,7 +100,9 @@ Opciones comunes: { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` @@ -112,7 +112,7 @@ Opciones comunes: | Tipo | Qué hace | Opción clave | |:--|:--|:--| | `formula` | Valor derivado, evaluado al leer o al recalcular | `expression: F\`record.qty * record.unit_price\`` — consulta [CEL](./cel) | -| `summary` | Acumulación sobre una relación hija | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | Acumulación sobre una relación hija | `summaryOperations: { object, field, function, filter? }` (`count` \| `sum` \| `avg` \| `min` \| `max`; el `filter` opcional — una condición `where` de consulta, p. ej. `{ status: 'received' }` — agrega solo las filas hijas coincidentes, 16.0+) | | `autonumber` | Número de visualización autoincremental | `format` (p. ej. `TKT-{0000}`), `startAt` | Ejemplo de fórmula: @@ -129,19 +129,18 @@ Ejemplo de fórmula: | Tipo | Para qué usarlo | Opción clave | |:--|:--|:--| -| `image` | adjuntos de imagen | `fileAttachmentConfig` (ver abajo) | +| `image` | adjuntos de imagen | `multiple` / `accept` / `maxSize` (ver abajo) | | `file` | cualquier archivo | igual | | `avatar` | fotos de perfil | recorte cuadrado, valores predeterminados sensatos | | `video` | subidas de video | captura de duración + miniatura | | `audio` | audio | vista previa de forma de onda | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` @@ -167,7 +166,7 @@ fileAttachmentConfig: { | `qrcode` | renderiza el valor como un código QR o de barras (EAN / UPC / Code128) | | `progress` | porcentaje derivado renderizado como una barra | | `tags` | arreglo de etiquetas de formato libre con autocompletado | -| `vector` | columna de embedding — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | columna de embedding — `dimensions` plano (p. ej. `1536` para embeddings de OpenAI); el `vectorConfig` anidado (`distanceMetric`/`indexed`/`indexType`) se eliminó en 16.0 | ## Campos del sistema (inyectados automáticamente en cada objeto) diff --git a/content/docs/reference/field-types.fr.mdx b/content/docs/reference/field-types.fr.mdx index 919e3b0..ae9bb22 100644 --- a/content/docs/reference/field-types.fr.mdx +++ b/content/docs/reference/field-types.fr.mdx @@ -18,11 +18,9 @@ description: Chaque type de champ que vous pouvez déclarer sur un objet — ce | `searchable` | boolean | `false` | Indexé pour `/api/v1/search` | | `multiple` | boolean | `false` | Stocke un tableau de valeurs | | `defaultValue` | unknown | — | Valeur initiale (littéral ou CEL) | -| `columnName` | string | = `name` | Remplace la colonne physique en base de données | | `hidden` | boolean | `false` | Masquer dans les vues Console par défaut | | `readonly` | boolean | `false` | Désactiver dans les formulaires | | `system` | boolean | `false` | Injecté automatiquement (id, created_at, …) | -| `index` | boolean | `false` | Créer un index en base de données | | `externalId` | boolean | `false` | Éligible à l'upsert via clé externe | | `inlineHelpText` | string | — | Infobulle / texte d'aide | | `conditionalRequired` | prédicat `P` | — | Requis lorsque le CEL est vrai | @@ -102,7 +100,9 @@ Options courantes : { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` @@ -112,7 +112,7 @@ Options courantes : | Type | Ce qu'il fait | Option clé | |:--|:--|:--| | `formula` | Valeur dérivée, évaluée à la lecture ou au recalcul | `expression: F\`record.qty * record.unit_price\`` — voir [CEL](./cel) | -| `summary` | Agrégation sur une relation enfant | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | Agrégation sur une relation enfant | `summaryOperations: { object, field, function, filter? }` (`count` \| `sum` \| `avg` \| `min` \| `max` ; le `filter` optionnel — une condition `where` de requête, p. ex. `{ status: 'received' }` — n'agrège que les lignes enfants correspondantes, 16.0+) | | `autonumber` | Numéro d'affichage auto-incrémenté | `format` (par ex. `TKT-{0000}`), `startAt` | Exemple de formule : @@ -129,19 +129,18 @@ Exemple de formule : | Type | À utiliser pour | Option clé | |:--|:--|:--| -| `image` | pièces jointes image | `fileAttachmentConfig` (voir ci-dessous) | +| `image` | pièces jointes image | `multiple` / `accept` / `maxSize` (voir ci-dessous) | | `file` | tout fichier | identique | | `avatar` | photos de profil | recadrage carré, valeurs par défaut sensées | | `video` | téléversements vidéo | durée + capture de miniature | | `audio` | audio | aperçu en forme d'onde | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` @@ -167,7 +166,7 @@ fileAttachmentConfig: { | `qrcode` | affiche la valeur sous forme de QR ou code-barres (EAN / UPC / Code128) | | `progress` | pourcentage dérivé affiché sous forme de barre | | `tags` | tableau de tags libres avec autocomplétion | -| `vector` | colonne d'embedding — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | colonne d'embedding — `dimensions` à plat (p. ex. `1536` pour les embeddings OpenAI) ; le `vectorConfig` imbriqué (`distanceMetric`/`indexed`/`indexType`) a été supprimé en 16.0 | ## Champs système (injectés automatiquement sur chaque objet) diff --git a/content/docs/reference/field-types.ja.mdx b/content/docs/reference/field-types.ja.mdx index a8a4781..035217b 100644 --- a/content/docs/reference/field-types.ja.mdx +++ b/content/docs/reference/field-types.ja.mdx @@ -18,11 +18,9 @@ description: オブジェクトに宣言できるすべてのフィールドタ | `searchable` | boolean | `false` | `/api/v1/search` 用にインデックス化 | | `multiple` | boolean | `false` | 値の配列を格納 | | `defaultValue` | unknown | — | 初期値(リテラルまたは CEL) | -| `columnName` | string | = `name` | 物理 DB カラムを上書き | | `hidden` | boolean | `false` | デフォルトの Console ビューから非表示 | | `readonly` | boolean | `false` | フォームで無効化 | | `system` | boolean | `false` | 自動注入(id, created_at, …) | -| `index` | boolean | `false` | DB インデックスを作成 | | `externalId` | boolean | `false` | 外部キーによる upsert の対象 | | `inlineHelpText` | string | — | ツールチップ / ヘルパーテキスト | | `conditionalRequired` | `P` 述語 | — | CEL が真のとき必須 | @@ -102,7 +100,9 @@ options: [ { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` @@ -112,7 +112,7 @@ options: [ | 型 | 機能 | 主なオプション | |:--|:--|:--| | `formula` | 導出値。読み取り時または再計算時に評価される | `expression: F\`record.qty * record.unit_price\`` — [CEL](./cel) を参照 | -| `summary` | 子リレーションのロールアップ | `summaryOperations: { object, field, function }`(`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | 子リレーションのロールアップ | `summaryOperations: { object, field, function, filter? }`(`count` \| `sum` \| `avg` \| `min` \| `max`。省略可能な `filter` — クエリの `where` 条件、例 `{ status: 'received' }` — は条件に一致する子行のみを集計、16.0+) | | `autonumber` | 自動採番される表示番号 | `format`(例: `TKT-{0000}`)、`startAt` | Formula の例: @@ -129,19 +129,18 @@ Formula の例: | 型 | 用途 | 主なオプション | |:--|:--|:--| -| `image` | 画像添付 | `fileAttachmentConfig`(下記参照) | +| `image` | 画像添付 | `multiple` / `accept` / `maxSize`(下記参照) | | `file` | 任意のファイル | 同上 | | `avatar` | プロフィール画像 | 正方形クロップ、適切なデフォルト | | `video` | 動画アップロード | 再生時間 + サムネイルキャプチャ | | `audio` | 音声 | 波形プレビュー | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` @@ -167,7 +166,7 @@ fileAttachmentConfig: { | `qrcode` | 値を QR またはバーコード(EAN / UPC / Code128)としてレンダリング | | `progress` | 導出されたパーセントをバーとして表示 | | `tags` | オートコンプリート付きの自由形式タグ配列 | -| `vector` | 埋め込みカラム — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | 埋め込みカラム — フラットな `dimensions`(例: OpenAI 埋め込みは `1536`)。ネストされた `vectorConfig`(`distanceMetric`/`indexed`/`indexType`)は 16.0 で削除 | ## システムフィールド(すべてのオブジェクトに自動注入) diff --git a/content/docs/reference/field-types.ko.mdx b/content/docs/reference/field-types.ko.mdx index 375804a..0ada2ea 100644 --- a/content/docs/reference/field-types.ko.mdx +++ b/content/docs/reference/field-types.ko.mdx @@ -18,11 +18,9 @@ description: 객체에 선언할 수 있는 모든 필드 타입 — 저장하 | `searchable` | boolean | `false` | `/api/v1/search`용 인덱싱 | | `multiple` | boolean | `false` | 값의 배열로 저장 | | `defaultValue` | unknown | — | 초기값 (리터럴 또는 CEL) | -| `columnName` | string | = `name` | 물리적 DB 컬럼 재정의 | | `hidden` | boolean | `false` | 기본 Console 뷰에서 숨김 | | `readonly` | boolean | `false` | 폼에서 비활성화 | | `system` | boolean | `false` | 자동 주입됨 (id, created_at, …) | -| `index` | boolean | `false` | DB 인덱스 생성 | | `externalId` | boolean | `false` | 외부 키를 통한 upsert 대상 | | `inlineHelpText` | string | — | 툴팁 / 도움말 텍스트 | | `conditionalRequired` | `P` 술어 | — | CEL이 true일 때 필수 | @@ -102,7 +100,9 @@ options: [ { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` @@ -112,7 +112,7 @@ options: [ | 타입 | 동작 | 주요 옵션 | |:--|:--|:--| | `formula` | 파생 값, 읽기 또는 재계산 시 평가됨 | `expression: F\`record.qty * record.unit_price\`` — [CEL](./cel) 참조 | -| `summary` | 자식 관계에 대한 롤업 | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | 자식 관계에 대한 롤업 | `summaryOperations: { object, field, function, filter? }` (`count` \| `sum` \| `avg` \| `min` \| `max`; 선택적 `filter` — 쿼리 `where` 조건, 예: `{ status: 'received' }` — 는 조건에 맞는 자식 행만 집계, 16.0+) | | `autonumber` | 자동 증가 표시 번호 | `format` (예: `TKT-{0000}`), `startAt` | 수식 예시: @@ -129,19 +129,18 @@ options: [ | 타입 | 용도 | 주요 옵션 | |:--|:--|:--| -| `image` | 이미지 첨부 | `fileAttachmentConfig` (아래 참조) | +| `image` | 이미지 첨부 | `multiple` / `accept` / `maxSize` (아래 참조) | | `file` | 모든 파일 | 동일 | | `avatar` | 프로필 사진 | 정사각형 크롭, 합리적인 기본값 | | `video` | 비디오 업로드 | 재생 시간 + 썸네일 캡처 | | `audio` | 오디오 | 파형 미리보기 | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` @@ -167,7 +166,7 @@ fileAttachmentConfig: { | `qrcode` | 값을 QR 또는 바코드(EAN / UPC / Code128)로 렌더링 | | `progress` | 막대로 렌더링되는 파생 백분율 | | `tags` | 자동 완성이 있는 자유 형식 태그 배열 | -| `vector` | 임베딩 컬럼 — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | 임베딩 컬럼 — 평탄한 `dimensions` (예: OpenAI 임베딩은 `1536`); 중첩된 `vectorConfig`(`distanceMetric`/`indexed`/`indexType`)는 16.0에서 제거됨 | ## 시스템 필드 (모든 객체에 자동 주입됨) diff --git a/content/docs/reference/field-types.mdx b/content/docs/reference/field-types.mdx index f366cbd..4cba16e 100644 --- a/content/docs/reference/field-types.mdx +++ b/content/docs/reference/field-types.mdx @@ -18,16 +18,18 @@ description: Every field type you can declare on an object — what it stores, w | `searchable` | boolean | `false` | Indexed for `/api/v1/search` | | `multiple` | boolean | `false` | Store an array of values | | `defaultValue` | unknown | — | Initial value (literal or CEL) | -| `columnName` | string | = `name` | Override physical DB column | | `hidden` | boolean | `false` | Hide from default Console views | | `readonly` | boolean | `false` | Disable in forms | | `system` | boolean | `false` | Auto-injected (id, created_at, …) | -| `index` | boolean | `false` | Create a DB index | | `externalId` | boolean | `false` | Eligible for upsert via external key | | `inlineHelpText` | string | — | Tooltip / helper text | | `conditionalRequired` | `P` predicate | — | Required when CEL is true | | `trackHistory` | boolean | `false` | Render value changes as human-readable entries on the record activity timeline (ADR-0052) | +> Removed in 16.0: `columnName` (the physical column is always the field name; +> external/federated objects map columns via `external.columnMap`) and the +> field-level `index` boolean (declare indexes in the object's `indexes[]` array). + ## Text family | Type | Use for | Key options | @@ -102,17 +104,23 @@ Common options: { type: 'lookup', reference: 'account', // target object name - referenceFilters: ['status:active'], // narrow the lookup picker + lookupFilters: [ // narrow the lookup picker + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` +> The old string-array `referenceFilters` was removed in 16.0 — the record picker +> only honours the structured `lookupFilters` form. Supported `operator` values: +> `eq`, `ne`, `gt`, `lt`, `gte`, `lte`, `contains`, `in`, `notIn`. + ## Computed | Type | What it does | Key option | |:--|:--|:--| | `formula` | Derived value, evaluated on read or recalc | `expression: F\`record.qty * record.unit_price\`` — see [CEL](./cel) | -| `summary` | Roll-up over a child relation | `summaryOperations: { object, field, function }` (`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | Roll-up over a child relation | `summaryOperations: { object, field, function, filter? }` (`count` \| `sum` \| `avg` \| `min` \| `max`; optional `filter` — a query-`where` condition, e.g. `{ status: 'received' }` — aggregates only matching child rows, 16.0+) | | `autonumber` | Auto-incrementing display number | `format` (e.g. `TKT-{0000}`), `startAt` | Formula example: @@ -129,22 +137,25 @@ Formula example: | Type | Use for | Key option | |:--|:--|:--| -| `image` | image attachments | `fileAttachmentConfig` (see below) | +| `image` | image attachments | `multiple` / `accept` / `maxSize` (see below) | | `file` | any file | same | | `avatar` | profile pictures | square crop, sensible defaults | | `video` | video uploads | duration + thumbnail capture | | `audio` | audio | waveform preview | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // bytes - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // see Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // store an array of attachments + accept: ['image/png', 'image/jpeg'], // MIME types / extensions + maxSize: 10_000_000 // bytes } ``` +> The nested `fileAttachmentConfig` object was removed in 16.0 — it was never +> read by the runtime. Use the flat `multiple` / `accept` / `maxSize` properties. +> Storage is configured per deployment (see Configure → Storage), not per field. + ## Structured | Type | Stores | Notes | @@ -167,7 +178,7 @@ fileAttachmentConfig: { | `qrcode` | renders the value as a QR or barcode (EAN / UPC / Code128) | | `progress` | derived percent rendered as a bar | | `tags` | free-form tag array with autocomplete | -| `vector` | embedding column — `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | embedding column — flat `dimensions` (e.g. `1536` for OpenAI embeddings). The nested `vectorConfig` (`distanceMetric`/`indexed`/`indexType`) was removed in 16.0 — it was never read | ## System fields (auto-injected on every object) diff --git a/content/docs/reference/field-types.zh-Hans.mdx b/content/docs/reference/field-types.zh-Hans.mdx index 85495c6..e7674fc 100644 --- a/content/docs/reference/field-types.zh-Hans.mdx +++ b/content/docs/reference/field-types.zh-Hans.mdx @@ -18,16 +18,18 @@ description: 你可以在对象上声明的所有字段类型 —— 存储什 | `searchable` | boolean | `false` | 为 `/api/v1/search` 建索引 | | `multiple` | boolean | `false` | 存储值的数组 | | `defaultValue` | unknown | — | 初始值(字面量或 CEL) | -| `columnName` | string | = `name` | 覆盖物理 DB 列 | | `hidden` | boolean | `false` | 在默认 Console 视图中隐藏 | | `readonly` | boolean | `false` | 表单中禁用 | | `system` | boolean | `false` | 自动注入(id、created_at……) | -| `index` | boolean | `false` | 创建 DB 索引 | | `externalId` | boolean | `false` | 可用作外部键 upsert | | `inlineHelpText` | string | — | 提示/帮助文本 | | `conditionalRequired` | `P` 谓词 | — | CEL 为真时必填 | | `trackHistory` | boolean | `false` | 将值变更渲染为记录活动时间线上可读的条目(ADR-0052) | +> 16.0 中已移除:`columnName`(物理列始终等于字段名;外部/联邦对象通过 +> `external.columnMap` 映射列)以及字段级 `index` 布尔值(请在对象的 +> `indexes[]` 数组中声明索引)。 + ## 文本家族 | 类型 | 用途 | 关键选项 | @@ -101,17 +103,23 @@ options: [ { type: 'lookup', reference: 'account', // 目标对象名 - referenceFilters: ['status:active'], // 收窄查找选择器 + lookupFilters: [ // 收窄查找选择器 + { field: 'status', operator: 'eq', value: 'active' } + ], deleteBehavior: 'set_null' // 'set_null' | 'cascade' | 'restrict' } ``` +> 旧的字符串数组 `referenceFilters` 已在 16.0 移除——记录选择器只识别结构化的 +> `lookupFilters` 形式。支持的 `operator` 取值:`eq`、`ne`、`gt`、`lt`、`gte`、 +> `lte`、`contains`、`in`、`notIn`。 + ## 计算 | 类型 | 作用 | 关键选项 | |:--|:--|:--| | `formula` | 派生值,读取或重新计算时求值 | `expression: F\`record.qty * record.unit_price\`` —— 见 [CEL](./cel) | -| `summary` | 子关系上的汇总 | `summaryOperations: { object, field, function }`(`count` \| `sum` \| `avg` \| `min` \| `max`) | +| `summary` | 子关系上的汇总 | `summaryOperations: { object, field, function, filter? }`(`count` \| `sum` \| `avg` \| `min` \| `max`;可选 `filter` —— 查询 `where` 条件,如 `{ status: 'received' }` —— 仅聚合匹配的子行,16.0+) | | `autonumber` | 自动递增显示编号 | `format`(如 `TKT-{0000}`)、`startAt` | 公式示例: @@ -128,22 +136,25 @@ options: [ | 类型 | 用途 | 关键选项 | |:--|:--|:--| -| `image` | 图像附件 | `fileAttachmentConfig`(见下) | +| `image` | 图像附件 | `multiple` / `accept` / `maxSize`(见下) | | `file` | 任意文件 | 同 | | `avatar` | 头像 | 方形裁剪,合理默认 | | `video` | 视频上传 | 时长 + 缩略图捕获 | | `audio` | 音频 | 波形预览 | ```ts -fileAttachmentConfig: { - maxSize: 10_000_000, // 字节 - allowedTypes: ['image/png','image/jpeg'], - virusScan: true, - storageProvider: 's3', // 见 Configure → Storage - imageValidation: { minWidth: 200, maxWidth: 4096, generateThumbnails: ['sm','md','lg'] } +{ + type: 'file', + multiple: true, // 存储附件数组 + accept: ['image/png', 'image/jpeg'], // MIME 类型 / 扩展名 + maxSize: 10_000_000 // 字节 } ``` +> 嵌套的 `fileAttachmentConfig` 对象已在 16.0 移除——运行时从未读取过它。 +> 请使用扁平的 `multiple` / `accept` / `maxSize` 属性。存储按部署配置 +> (见 Configure → Storage),而非按字段配置。 + ## 结构化 | 类型 | 存储 | 说明 | @@ -166,7 +177,7 @@ fileAttachmentConfig: { | `qrcode` | 将值渲染为 QR 或条形码(EAN / UPC / Code128) | | `progress` | 派生百分比,以进度条渲染 | | `tags` | 带自动完成的自由标签数组 | -| `vector` | 嵌入列 —— `vectorConfig: { dimensions, distanceMetric: 'cosine' \| 'euclidean', indexed, indexType: 'hnsw' \| 'ivfflat' }` | +| `vector` | 嵌入列 —— 扁平的 `dimensions`(如 OpenAI 嵌入为 `1536`);嵌套的 `vectorConfig`(`distanceMetric`/`indexed`/`indexType`)已在 16.0 移除 | ## 系统字段(每个对象自动注入) diff --git a/content/docs/resources/changelog.mdx b/content/docs/resources/changelog.mdx index 479040c..0d19acc 100644 --- a/content/docs/resources/changelog.mdx +++ b/content/docs/resources/changelog.mdx @@ -49,9 +49,81 @@ Subscribe to releases on GitHub to get notified. ## Recent highlights -### 14.x — current release train +### 16.0 -ObjectOS One and the bundled server now ship on `@objectstack` **14.7.0**. +`@objectstack` **16.0.0** converges the developer surface and makes +declared metadata honest: one blessed name for the caller's org, real +multi-approver governance, time-relative automations that actually fire, +and a platform-wide sweep that turns silently-ignored metadata into loud +errors at authoring time. Full notes: +[docs.objectstack.ai/docs/releases](https://docs.objectstack.ai/docs/releases). +What moved: + +- **`tenantId` alias removed from hook/action `ctx`** (16.0, breaking) — + read the caller's org as `ctx.user.organizationId` / + `ctx.session.organizationId` in every `*.hook.ts` / `*.action.ts` body + (the value is unchanged; `ctx.user` is `undefined` for system writes). + The driver-layer tenancy axis (`ExecutionContext.tenantId`) is a + different concept and is untouched. +- **Approvals: quorum, per-group sign-off (会签), and a metadata-driven + inbox** — approval steps support M-of-N quorum (`minApprovals`) and + one-approver-from-each-group steps, tallied against an open-time + snapshot with OOO substitution; a single rejection still vetoes, and + thresholds clamp so misconfiguration can never deadlock. Decisions + carry file attachments, progress ("2 of 3 · finance pending") is + computed server-side, and approve / reject / reassign / send-back / + request-info / remind / recall / resubmit are declared `type:'api'` + actions on `sys_approval_request` — the Console renders them through + its generic action runtime instead of hand-written buttons. +- **Time-relative automations** — a flow start node can declare + `timeRelative` (`offsetDays: [60, 30, 7]` or `withinDays`); a daily + sweep launches the flow once per matching record, so "remind me 60 + days before the contract ends" finally just works — no more + date-equality conditions that only fire on a lucky edit. +- **Filtered roll-ups** — `summaryOperations.filter` lets a parent total + aggregate only the matching child rows, and re-aggregates when a child + moves in or out of the predicate. +- **Strict dashboard widgets** (16.0, breaking) — + `DashboardWidgetSchema` is `.strict()`: an undeclared key (typo or + removed inline-analytics key) is a parse error naming the key and + pointing at the dataset shape (`dataset` + `dimensions` + `values`) + instead of silently rendering nothing. +- **MCP: stdio gets an identity, agents get a linter** — stdio + auto-start moves to its own `OS_MCP_STDIO_ENABLED` switch (default + off) and requires an `OS_MCP_STDIO_API_KEY=osk_...` principal, + fail-closed, resolved through the same authorization chain as HTTP + (RLS/FLS/tenant apply; no `system` bypass). A new + `validate_expression` tool lets agents lint a formula against the real + object schema before saving it. +- **Enforce-or-remove sweep** (16.0, breaking) — declared-but-unenforced + metadata is now loud: dead field/object/agent props are removed or + tombstoned with guidance, hook events collapse 18 → 8, validation + rules lose the never-evaluated `'delete'` event, webhook `undelete` / + `api` triggers are removed, and unknown `requires` capability tokens + are rejected at authoring (the `aiStudio` / `aiSeat` aliases are gone — + use kebab-case `ai-studio` / `ai-seat`). +- **Engine-owned system rows are read-only through the generic data API** + (ADR-0103) — jobs, notifications, approval-runtime rows, sharing rows, + audit trails, secrets, and the like are locked to `get`/`list`, with a + fail-closed guard rejecting user-context writes through `/data`. + Third-party `system` objects that legitimately take user writes must + declare `userActions`. +- **Date logic stops lying** — `record.due_date == today()` now matches + (temporal equality comparisons are rewritten to coerce the field + operand), while date *arithmetic* in formulas (`end - start + 1`, + `today() + 30`) becomes a build-time error pointing at `daysBetween` / + `daysFromNow` / `addDays` / `addMonths` — those expressions always + faulted to `null` at runtime anyway. +- **Bulk user import defaults to `passwordPolicy: 'auto'`** — rows with + a deliverable channel (real email + wired email service, or phone + + SMS invite path) are invited; only unreachable rows get a one-time + temporary password (`must_change_password`). Pass + `passwordPolicy: 'none'` explicitly for the old identity-only + behavior; per-row outcomes are on `rows[].delivery`. + +### 14.x + +ObjectOS One and the bundled server shipped on `@objectstack` **14.7.0**. The runtime boot contract is unchanged — `createStandaloneStack` takes the same artifact, environment, and database settings — but 13.0 and 14.0 both land breaking changes in the authorization vocabulary, so **review your diff --git a/content/docs/resources/changelog.zh-Hans.mdx b/content/docs/resources/changelog.zh-Hans.mdx index 864d56a..a5882ff 100644 --- a/content/docs/resources/changelog.zh-Hans.mdx +++ b/content/docs/resources/changelog.zh-Hans.mdx @@ -49,9 +49,65 @@ ObjectOS 遵循 **[语义化版本](https://semver.org/)**:`MAJOR.MINOR.PATCH` ## 近期亮点 -### 14.x —— 当前发布列车 +### 16.0 -ObjectOS One 与捆绑的 server 现已基于 `@objectstack` **14.7.0**。运行时的启动 +`@objectstack` **16.0.0** 收敛了开发者面,并让已声明的元数据变得诚实:调用者组织只有一个受祝福的名字、审批获得真正的多审批人治理、时间相对自动化真正会触发,以及一次平台级清理,把被静默忽略的元数据在创作时就变成响亮的错误。完整说明见 +[docs.objectstack.ai/docs/releases](https://docs.objectstack.ai/docs/releases)。 +16.0 的变动: + +- **hook/action `ctx` 移除 `tenantId` 别名**(16.0,破坏性)—— 在所有 + `*.hook.ts` / `*.action.ts` 代码体中,改用 `ctx.user.organizationId` / + `ctx.session.organizationId` 读取调用者组织(值不变;system 写入时 + `ctx.user` 为 `undefined`)。驱动层的租户轴(`ExecutionContext.tenantId`) + 是另一个概念,刻意保持不动。 +- **审批:法定人数、会签,以及元数据驱动的收件箱**——审批步骤支持 + M-of-N 法定人数(`minApprovals`)和每组一人的会签,基于开启时刻的快照 + 统计并支持 OOO 替补;单次拒绝仍是一票否决,阈值会自动收紧,错误配置 + 永远不会造成死锁。决策可附带文件附件,进度("2 of 3 · finance + pending")由服务端计算,approve / reject / reassign / send-back / + request-info / remind / recall / resubmit 都作为 `sys_approval_request` + 上声明的 `type:'api'` action —— Console 通过通用 action 运行时渲染它们, + 而不是手写按钮。 +- **时间相对自动化**——流程开始节点可以声明 `timeRelative` + (`offsetDays: [60, 30, 7]` 或 `withinDays`);每日扫描对每条匹配记录 + 启动一次流程,"合同到期前 60 天提醒我"终于开箱即用——不再依赖只有 + 恰好在正确的那天有人编辑记录才会触发的日期相等条件。 +- **带过滤的汇总字段**——`summaryOperations.filter` 让父级合计只聚合 + 匹配的子行,并在子行进出谓词范围时重新聚合。 +- **严格的仪表盘 widget**(16.0,破坏性)——`DashboardWidgetSchema` 改为 + `.strict()`:未声明的键(拼写错误或已移除的内联分析键)会成为解析 + 错误,指名该键并指向数据集形态(`dataset` + `dimensions` + `values`), + 而不是静默渲染出一片空白。 +- **MCP:stdio 获得身份,Agent 获得校验器**——stdio 自动启动改由独立的 + `OS_MCP_STDIO_ENABLED` 开关控制(默认关闭),并要求 + `OS_MCP_STDIO_API_KEY=osk_...` 身份主体,fail-closed,通过与 HTTP 相同 + 的授权链解析(RLS/FLS/租户隔离全部生效;没有 `system` 旁路)。新增 + `validate_expression` 工具,让 Agent 在保存前对照真实对象 schema 校验 + 公式。 +- **enforce-or-remove 清理**(16.0,破坏性)——声明了却从不强制执行的 + 元数据现在会大声报错:无效的字段/对象/agent 属性被移除或以带指引的 + tombstone 报错,hook 事件从 18 收敛到 8,验证规则移除从不求值的 + `'delete'` 事件,webhook 的 `undelete` / `api` 触发器被移除,未知的 + `requires` 能力 token 在创作时即被拒绝(`aiStudio` / `aiSeat` 别名已 + 移除——使用 kebab-case 的 `ai-studio` / `ai-seat`)。 +- **引擎自有的系统行通过通用数据 API 只读**(ADR-0103)——作业、通知、 + 审批运行时行、共享行、审计日志、密钥等被锁定为 `get`/`list`, + fail-closed 守卫会拒绝经 `/data` 的用户上下文写入。确实需要接受用户 + 写入的第三方 `system` 对象必须声明 `userActions`。 +- **日期逻辑不再说谎**——`record.due_date == today()` 现在能匹配了 + (时间相等比较会被重写以强制转换字段操作数);而公式中的日期*算术* + (`end - start + 1`、`today() + 30`)改为构建期错误,并指向 + `daysBetween` / `daysFromNow` / `addDays` / `addMonths` ——这些表达式 + 在运行时本来就总是得到 `null`。 +- **批量用户导入默认 `passwordPolicy: 'auto'`**——具有可送达通道的行 + (真实邮箱 + 已接通的邮件服务,或手机号 + 短信邀请通道)会被邀请; + 只有无法触达的行才得到一次性临时密码(`must_change_password`)。 + 想要旧的仅建身份行为,请显式传入 `passwordPolicy: 'none'`;每行结果 + 在 `rows[].delivery` 上。 + +### 14.x + +ObjectOS One 与捆绑的 server 基于 `@objectstack` **14.7.0** 发布。运行时的启动 契约并未变化 —— `createStandaloneStack` 仍然接收同样的 artifact、环境与数据库 设置 —— 但 13.0 与 14.0 均在授权词汇上有破坏性变更,因此**从 ≤ 12.x 升级前请 先审查权限元数据**。完整说明见