diff --git a/content/docs/build/automation/flows.mdx b/content/docs/build/automation/flows.mdx index 9d786ac..c31bef7 100644 --- a/content/docs/build/automation/flows.mdx +++ b/content/docs/build/automation/flows.mdx @@ -44,7 +44,7 @@ lacks either one, because such a flow would register and never run. | `schedule`, time-relative *(16.0)* | A date field's proximity to today, via a daily sweep | "Remind me 60 days before the contract ends" | | `autolaunched` | An action button, another flow, or an API call | "Approve invoice" actions | | `screen` | A user, through one or more screens | Guided data entry | -| `api` | An HTTP request | An endpoint for an external system | +| `api` | An HTTP request, signed with the start node's `config.secret` | An endpoint for an external system | ## Record change: react to a record change @@ -163,6 +163,22 @@ time-triggered flow has no caller to inherit an organization from; ObjectStack's [Flow metadata](https://docs.objectstack.ai/docs/automation/flows#the-acting-organization) says when its start node must declare one with `organization`. +> **Scheduled work is off until the deployment turns it on.** Since +> ObjectStack 17.5, a time-triggered flow (a `schedule` cadence, or the +> time-relative sweep below) and a packaged `defineJob` cron job arm only +> when the deployment sets `OS_AUTOMATION_SCHEDULED_WORK_ENABLED` to `true` +> (or `1`, `on`, `yes`). Unset, nothing arms, and each such flow is reported +> as *disabled by deployment policy*, not as an error. Off is the ObjectStack +> runtime's default; whether your ObjectOS image or its deploy-bundle +> template sets the variable for you is not something this page can promise, +> so set it explicitly when your app depends on scheduled flows, and confirm +> it with `os doctor`, which prints the effective value — see +> [Environment Variables](/docs/reference/environment-variables#runtime-services). +> The platform's own scheduled work (approval escalation, retention, message +> dispatch) is not affected. Under the `isolated` tenancy posture, a +> scheduled flow that does not declare `organization` on its start node is +> not armed even with the switch on. + ## Time-relative: fire relative to a date field (16.0) "Remind me 60 days before the contract ends" used to be authored as a @@ -295,6 +311,15 @@ under the same names; only declared inputs are bound. The caller's identity is forwarded into the run, so the update runs under the caller's permissions. +The route answers with an HTTP status that says what happened, so branch +on the status rather than on a `success` flag in the body: `200` for a run +that dispatched (completed, paused, or [refused](#ending-a-run-completed-or-refused)), +`404` for an unknown flow, `409 FLOW_DISABLED` for a flow that is switched +off, `422 FLOW_NO_START_NODE` for a definition with no start node, and +`400 FLOW_FAILED` for a run that dispatched and failed. An action of type +`flow` answers the same way — see +[REST API → Flow status table](/docs/reference/rest-api#flow-status-table). + ## Node types | Node `type` | Purpose | @@ -314,6 +339,34 @@ permissions. | `screen` | Pause for a user's input | | `subflow` | Call another flow | | `approval` | Block until approvers decide (requires `@objectstack/plugin-approvals`) | +| `end` | End the run — `completed` by default, or `refused` with a reason (below) | + +### Ending a run: completed or refused + +Since ObjectStack 17.4 an `end` node declares the run's **outcome**. A flow +that has to say no — a duplicate, an over-limit amount — ends on a refusing +`end` instead of failing or showing a screen that only says so: + +```ts +// One flow node; the flow around it is omitted. +{ + id: 'refuse_duplicate', + type: 'end', + label: 'Refused: duplicate', + config: { + outcome: 'refused', // 'completed' (the default) | 'refused' + message: 'Refused: {record.name} is already registered.', + }, +} +``` + +A refused run is not a failed one: nothing threw. It ends with the run +status `refused`, the trigger route answers `200` with that status, and the +rendered `message` comes back as `refusalMessage`, with no +`successMessage` beside it. `message` is required when +`outcome` is `refused`, and refused on a completed end, where nothing +would ever show it. Reach the refusing `end` from a `decision` branch, and +keep every write on the branches the refusal does not take. ## Conditions and branches @@ -358,7 +411,11 @@ export const expenseRouting = defineFlow({ A `decision` node branches on its out-edges. They are evaluated in the order `edges` lists them, the first whose `condition` holds runs, and the `isDefault` edge runs when none does. Without `isDefault`, an edge with no -condition runs on every pass, beside whichever branch matched. +condition runs on every pass, beside whichever branch matched. To run +*every* branch whose condition holds, declare `config: { mode: 'inclusive' }` +on the decision. First-match is the rule since ObjectStack 17.5; before it, +every true branch ran, and a decision stored in the database (one saved +from Studio) took the new meaning on upgrade without being rewritten. ## Error handling @@ -413,6 +470,17 @@ declarative metadata — non-engineers can edit a flow, and it serializes back to the same shape as the TypeScript you'd hand-author. It is Studio's **Automations** pillar. +A flow saved from Studio is stored in the deployment, beside the flows +your installed packages ship. Two rules follow, both since ObjectStack +17.6: + +- **A packaged flow wins its name.** A stored flow that shares its name + with a packaged flow is shadowed and does not run; the startup log warns + about it. To customize a packaged flow, clone it under a new name. +- **Switch a stored flow with its `status`.** The on/off toggle switches + packaged flows only. A flow authored in the deployment is switched off by + saving it with `status: 'obsolete'`, and back on with `status: 'active'`. + ## Testing flows ```bash diff --git a/content/docs/build/data/validation-rules.mdx b/content/docs/build/data/validation-rules.mdx index 7da4e90..263f0f8 100644 --- a/content/docs/build/data/validation-rules.mdx +++ b/content/docs/build/data/validation-rules.mdx @@ -44,7 +44,7 @@ fields: { | `min` / `max` | Numeric range (number, currency, percent, rating, slider…) | | `minLength` / `maxLength` | Character bounds on text types | | `format` | Built-in shape checks — `email`, `url`, `phone` field types carry theirs by default | -| `readonly: true` | Server-enforced on update — a non-system write to the field is silently dropped (insert exempt) | +| `readonly: true` | Server-enforced on insert and update — a non-system write to the field is dropped and reported in the response's `droppedFields`, or refused outright when the write passes the `strictReadonlyWrites` option. To seed a read-only field at create time, write as the system: a seed, a system hook, or a flow with `runAs: 'system'` | Field types also validate themselves for free: `email` checks the `local@domain` shape, `url` requires a protocol, `select` values must @@ -66,7 +66,12 @@ po_number: Field.text({ ``` `visibleWhen`, `readonlyWhen`, and `requiredWhen` all take a CEL -predicate — see [Formulas](/docs/build/data/formulas). +predicate — see [Formulas](/docs/build/data/formulas). They read the +record's own fields only. A `requiredWhen` or `readonlyWhen` that cannot +be evaluated — a misspelled field, ordering over `null`, a read through a +lookup such as `record.account.tier` — refuses the write with +`VALIDATION_FAILED` instead of letting it through; a check that has to +read a related record belongs in a [validation rule](#reading-a-related-record). ## Object-level validation rules @@ -178,6 +183,35 @@ 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. +### Reading a related record + +Since ObjectStack 17.5 a `script` or `cross_field` rule can read **one +hop** through a `lookup`, `master_detail`, `user` or `tree` field: + +```ts +// One validation rule, an entry of the object's `validations` +{ + name: 'partner_deal_cap', + type: 'script', + severity: 'error', + message: 'Partner deals over 10,000 need a direct contract', + condition: "record.account.type == 'partner' && record.amount > 10000", + events: ['insert', 'update'], +} +``` + +The engine loads only the related fields the condition names, in one +batched read per reference field, and reads nothing when no rule +traverses. A second hop (`record.account.owner.email`) is refused, and so +is comparing the reference's id while traversing it. Field-level +predicates and row-level security stay on the record's own fields. + +A rule sees `record` and `previous` and nothing else: it cannot ask who is +saving. `current_user.can(...)` is answered elsewhere — see +[CEL → Permission checks](/docs/reference/cel#permission-checks) — and in a +validation rule it cannot be evaluated, so the rule refuses every write it +runs on. + ## Uniqueness: use an index, not a rule There is **no** `uniqueness` validation type — on purpose. A @@ -217,9 +251,10 @@ form and in the API error response. Make it actionable: Use `severity` to calibrate: `error` blocks the save, `warning` shows the message but lets the save through, `info` is purely informational. -> **Tip:** A predicate that can't be evaluated (parse error, unbound -> variable) is treated as a broken rule — logged and skipped rather -> than blocking every write. +> **Warning:** A rule whose predicate cannot be evaluated (a parse error, +> a misspelled field, a value it cannot compare) **fails closed**: the +> write is refused with `VALIDATION_FAILED`, never saved with the rule +> skipped. Test a new rule against real records before you publish it. ## Best practices diff --git a/content/docs/build/data/validation-rules.zh-Hans.mdx b/content/docs/build/data/validation-rules.zh-Hans.mdx deleted file mode 100644 index f8188f7..0000000 --- a/content/docs/build/data/validation-rules.zh-Hans.mdx +++ /dev/null @@ -1,203 +0,0 @@ ---- -title: 验证规则 -description: 必填字段、唯一约束与 CEL 驱动的规则,在平台层拦住坏数据 —— 错误消息由你掌控。 -translation: - source_sha: a45397014164190e9a2a7f4663502907aeb2ccaef56f453caeda9ac8e722d49e - guide_rev: 1 - mode: auto ---- - -**验证在每条写入路径上都会执行。**REST、Console 表单、ObjectQL —— 同一套规则处处生效,坏数据没有后门可钻。自 16.0 起也包括多行更新:批量更新会对每一条匹配的记录逐行执行规则。验证规则是作用于单条记录的确定性、同步、无副作用谓词:仅凭这次写入(更新时再加上更新前的记录)即可判定,不做任何 I/O。 - -验证是分层的 —— 能表达规则的最低层就是该用的层: - -| 层 | 表达什么 | 示例 | -|---|---|---| -| **字段修饰符** | 是否必填、是否唯一 | `required: true`、`unique: true` | -| **字段约束** | 单字段形态 | `min`、`max`、`maxLength`、`format` | -| **条件修饰符** | 依赖上下文的必填 | ``requiredWhen: P`record.amount > 10000` `` | -| **对象级验证规则** | 跨字段业务逻辑 | "折扣不能超过总额" | -| **唯一索引** | 组合 / 限定范围的唯一性 | `{ fields: ['code', 'organization'], unique: true }` | -| **生命周期 Hook** | 任意验证代码、删除守卫 | `beforeInsert` / `beforeUpdate` / `beforeDelete` | - -## 字段级:required、unique 与约束 - -通用修饰符适用于所有字段类型: - -```ts -import { ObjectSchema, Field } from '@objectstack/spec/data'; - -fields: { - email: Field.email({ label: 'Contact Email', required: true, unique: true }), - quantity: Field.number({ label: 'Quantity', min: 1, max: 9999 }), - code: Field.text({ label: 'Code', minLength: 3, maxLength: 20 }), -} -``` - -| 修饰符 | 行为 | -|---|---| -| `required: true` | 在运行时拒绝 `null` / `undefined` | -| `unique: true` | 数据库层唯一约束 —— 而不是有竞态的应用层检查 | -| `min` / `max` | 数值范围(number、currency、percent、rating、slider……) | -| `minLength` / `maxLength` | 文本类型的字符长度上下限 | -| `format` | 内置形态检查 —— `email`、`url`、`phone` 字段类型默认自带 | -| `readonly: true` | 更新时由服务端强制 —— 非系统写入该字段会被静默丢弃(插入不受限) | - -字段类型本身也自带免费验证:`email` 检查 `local@domain` 形态,`url` 要求带协议,`select` 的值必须匹配某个选项,`lookup` 强制被引用记录存在,`json` 必须能解析。每种默认行为见[字段类型参考](/docs/reference/field-types)。 - -### 条件式必填 / 只读 / 可见 - -是否必填可以通过 CEL 谓词依赖记录的其余部分: - -```ts -import { P } from '@objectstack/spec'; - -po_number: Field.text({ - label: 'PO Number', - requiredWhen: P`record.amount > 10000`, -}), -``` - -`visibleWhen`、`readonlyWhen`、`requiredWhen` 都接受一个 CEL 谓词 —— 见[公式](/docs/build/data/formulas)。 - -## 对象级验证规则 - -跨字段业务逻辑放在对象的 `validations` 数组里: - -```ts -export const Order = ObjectSchema.create({ - name: 'order', - fields: { - amount: Field.currency({ label: 'Amount', required: true }), - status: Field.select({ label: 'Status', options: [ /* ... */ ] }), - }, - - validations: [ - { - name: 'amount_positive', - type: 'script', - severity: 'error', - message: 'Amount must be greater than zero', - // CEL 谓词 —— TRUE 表示记录无效。 - condition: 'record.amount <= 0', - events: ['insert', 'update'], - }, - ], -}); -``` - -> **警告:**对 `script` 规则来说,`condition` 是**失败**谓词 —— 它求值为 TRUE 时,验证失败。要针对*坏*状态来写判断:`record.amount <= 0` 拒绝非正数金额。 - -### 公共属性 - -每种规则类型都共享这套基础形态: - -| 属性 | 必填 | 说明 | -|---|---|---| -| `name` | 是 | 唯一的规则名(`snake_case`) | -| `message` | 是 | 面向用户的错误消息 | -| `type` | 是 | `script`、`state_machine`、`format`、`cross_field`、`json_schema`、`conditional` | -| `severity` | 否 | `error`(阻止保存,默认)、`warning`(允许保存)、`info` | -| `events` | 否 | `insert`、`update` —— 默认 `['insert', 'update']`。`delete` 事件已在 16.0 中移除:求值器从未在删除路径上运行(删除没有可供验证的记录载荷),它一直是静默空操作 —— 删除守卫请用 `beforeDelete` Hook | -| `priority` | 否 | 0–9999,数字越小越先执行(默认 100) | -| `active` | 否 | 不删除即可开关(默认 `true`) | - -### 六种规则类型 - -| 类型 | 检查什么 | 关键配置 | -|---|---|---| -| `script` | 任意 CEL 谓词 | `condition`(TRUE = 无效) | -| `state_machine` | 允许的状态迁移 | `field`、`transitions` 映射、可选 `initialStates` | -| `format` | 单字段匹配正则或内置格式 | `field`、`regex` 或 `format: 'email' \| 'url' \| 'phone' \| 'json'` | -| `cross_field` | 字段之间的关系 | `fields`、`condition` | -| `json_schema` | JSON 字段匹配 JSON Schema | `field`、`schema` | -| `conditional` | 仅在谓词成立时应用嵌套规则 | `when`、`then`、可选 `otherwise` | - -两条你会反复用到的规则: - -```ts -// 状态机 —— 强制状态流转 -{ - name: 'order_status_transitions', - type: 'state_machine', - severity: 'error', - message: 'Invalid status transition', - field: 'status', - transitions: { - draft: ['submitted', 'cancelled'], - submitted: ['approved', 'rejected', 'cancelled'], - approved: ['completed'], - rejected: ['draft'], - cancelled: [], - completed: [], - }, - initialStates: ['draft'], // 16.0 新增:记录允许被“创建”为哪些状态 - events: ['insert', 'update'], // 包含 'insert' 才会检查 initialStates -} - -// 跨字段 —— 日期先后有序 -{ - name: 'date_range_valid', - type: 'cross_field', - severity: 'error', - message: 'End date must be after start date', - fields: ['start_date', 'end_date'], - condition: 'record.end_date <= record.start_date', - events: ['insert', 'update'], -} -``` - -**16.0 新增:**`state_machine` 规则可以声明 `initialStates` —— 记录允许被*创建*为哪些状态。插入时状态字段的值不在列表内会被拒绝(`invalid_initial_state`)。`transitions` 只约束更新,而 `select` 字段允许任何已声明的选项作为初始值,所以不加 `initialStates` 时记录可能"生在流程中间"(比如一创建就是 `approved`)。省略它则保持旧行为(插入时不检查初始状态)。规则的 `events` 必须包含 `insert`,该检查才会执行。 - -在更新时的条件里,`previous` 持有变更前的快照 —— `record.stage != previous.stage` 可检测到变更。 - -## 唯一性:用索引,不用规则 - -**没有** `uniqueness` 验证类型 —— 这是有意为之。先 SELECT 再 INSERT 的检查天然有竞态(TOCTOU);数据库唯一约束没有。唯一性要在数据层强制: - -```ts -// 字段级 -email: Field.email({ label: 'Contact Email', unique: true }), - -// 组合 / 限定范围的唯一性用索引 -indexes: [ - { fields: ['code', 'organization'], unique: true }, - // 需要限定范围 / 条件约束时加 `partial` -] -``` - -同样的逻辑也排除了异步 / 远程验证(那是客户端表单的关注点,放在写路径上还是 SSRF 与延迟隐患)和自定义处理器 —— 任意验证代码应该放进 `beforeInsert` / `beforeUpdate` 生命周期 Hook,删除时的守卫则放进 `beforeDelete` Hook。 - -## 自定义错误消息 - -`message` 就是规则触发时用户看到的内容 —— 出现在 Console 表单和 API 错误响应中。要写得可执行: - -| 弱 | 强 | -|---|---| -| "Invalid input" | "Close date is required for closed deals" | -| "Validation failed" | "Phone must match format: +1-XXX-XXX-XXXX" | -| "Error" | "Discount cannot exceed total" | - -用 `severity` 来校准:`error` 阻止保存,`warning` 显示消息但放行保存,`info` 纯提示。 - -> **提示:**无法求值的谓词(解析错误、未绑定变量)会被当作损坏的规则处理 —— 记录日志后跳过,而不是拦下每一次写入。 - -## 最佳实践 - -| 应该 | 不应该 | -|---|---| -| 在尽可能低的层做验证(字段 → 规则 → Hook) | 用 Hook 做字段修饰符就能表达的事 | -| 写清晰、可执行的消息 | 在多层重复同一个检查 | -| 用 `priority` 让廉价的格式检查先跑 | 构造过于复杂的条件 | -| 用真实数据测试规则 | 拦住正当的边界情况 | - -## 下一步 - -| 页面 | 原因 | -|---|---| -| [数据模型](/docs/build/data) | 这些规则所保护的对象与字段 | -| [公式](/docs/build/data/formulas) | `condition` 与 `requiredWhen` 背后的 CEL 语言 | -| [关系](/docs/build/data/relationships) | 跨对象的引用完整性 | -| [CEL 参考](/docs/reference/cel) | 条件可用的完整运算符与标准库参考 | -| [字段类型](/docs/reference/field-types) | 各字段类型的内置验证默认值 | -| [流程](/docs/build/automation/flows) | 用自动化响应(合法的)记录变更 | diff --git a/content/docs/build/data/validation-rules.zh-Hant.mdx b/content/docs/build/data/validation-rules.zh-Hant.mdx deleted file mode 100644 index 2b60b54..0000000 --- a/content/docs/build/data/validation-rules.zh-Hant.mdx +++ /dev/null @@ -1,204 +0,0 @@ ---- -# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant -title: 驗證規則 -description: 必填欄位、唯一約束與 CEL 驅動的規則,在平臺層攔住壞資料 —— 錯誤訊息由你掌控。 -translation: - source_sha: a45397014164190e9a2a7f4663502907aeb2ccaef56f453caeda9ac8e722d49e - guide_rev: 1 - mode: auto ---- - -**驗證在每條寫入路徑上都會執行。**REST、Console 表單、ObjectQL —— 同一套規則處處生效,壞資料沒有後門可鑽。自 16.0 起也包括多行更新:批次更新會對每一條匹配的記錄逐行執行規則。驗證規則是作用於單條記錄的確定性、同步、無副作用謂詞:僅憑這次寫入(更新時再加上更新前的記錄)即可判定,不做任何 I/O。 - -驗證是分層的 —— 能表達規則的最低層就是該用的層: - -| 層 | 表達什麼 | 示例 | -|---|---|---| -| **欄位修飾符** | 是否必填、是否唯一 | `required: true`、`unique: true` | -| **欄位約束** | 單欄位形態 | `min`、`max`、`maxLength`、`format` | -| **條件修飾符** | 依賴上下文的必填 | ``requiredWhen: P`record.amount > 10000` `` | -| **物件級驗證規則** | 跨欄位業務邏輯 | "折扣不能超過總額" | -| **唯一索引** | 組合 / 限定範圍的唯一性 | `{ fields: ['code', 'organization'], unique: true }` | -| **生命週期 Hook** | 任意驗證程式碼、刪除守衛 | `beforeInsert` / `beforeUpdate` / `beforeDelete` | - -## 欄位級:required、unique 與約束 - -通用修飾符適用於所有欄位型別: - -```ts -import { ObjectSchema, Field } from '@objectstack/spec/data'; - -fields: { - email: Field.email({ label: 'Contact Email', required: true, unique: true }), - quantity: Field.number({ label: 'Quantity', min: 1, max: 9999 }), - code: Field.text({ label: 'Code', minLength: 3, maxLength: 20 }), -} -``` - -| 修飾符 | 行為 | -|---|---| -| `required: true` | 在執行時拒絕 `null` / `undefined` | -| `unique: true` | 資料庫層唯一約束 —— 而不是有競態的應用層檢查 | -| `min` / `max` | 數值範圍(number、currency、percent、rating、slider……) | -| `minLength` / `maxLength` | 文本型別的字元長度上下限 | -| `format` | 內建形態檢查 —— `email`、`url`、`phone` 欄位型別預設自帶 | -| `readonly: true` | 更新時由服務端強制 —— 非系統寫入該欄位會被靜默丟棄(插入不受限) | - -欄位型別本身也自帶免費驗證:`email` 檢查 `local@domain` 形態,`url` 要求帶協議,`select` 的值必須匹配某個選項,`lookup` 強制被引用記錄存在,`json` 必須能解析。每種預設行為見[欄位型別參考](/docs/reference/field-types)。 - -### 條件式必填 / 只讀 / 可見 - -是否必填可以通過 CEL 謂詞依賴記錄的其餘部分: - -```ts -import { P } from '@objectstack/spec'; - -po_number: Field.text({ - label: 'PO Number', - requiredWhen: P`record.amount > 10000`, -}), -``` - -`visibleWhen`、`readonlyWhen`、`requiredWhen` 都接受一個 CEL 謂詞 —— 見[公式](/docs/build/data/formulas)。 - -## 物件級驗證規則 - -跨欄位業務邏輯放在物件的 `validations` 數組裡: - -```ts -export const Order = ObjectSchema.create({ - name: 'order', - fields: { - amount: Field.currency({ label: 'Amount', required: true }), - status: Field.select({ label: 'Status', options: [ /* ... */ ] }), - }, - - validations: [ - { - name: 'amount_positive', - type: 'script', - severity: 'error', - message: 'Amount must be greater than zero', - // CEL 谓词 —— TRUE 表示记录无效。 - condition: 'record.amount <= 0', - events: ['insert', 'update'], - }, - ], -}); -``` - -> **警告:**對 `script` 規則來說,`condition` 是**失敗**謂詞 —— 它求值為 TRUE 時,驗證失敗。要針對*壞*狀態來寫判斷:`record.amount <= 0` 拒絕非正數金額。 - -### 公共屬性 - -每種規則型別都共享這套基礎形態: - -| 屬性 | 必填 | 說明 | -|---|---|---| -| `name` | 是 | 唯一的規則名(`snake_case`) | -| `message` | 是 | 面向使用者的錯誤訊息 | -| `type` | 是 | `script`、`state_machine`、`format`、`cross_field`、`json_schema`、`conditional` | -| `severity` | 否 | `error`(阻止儲存,預設)、`warning`(允許儲存)、`info` | -| `events` | 否 | `insert`、`update` —— 預設 `['insert', 'update']`。`delete` 事件已在 16.0 中移除:求值器從未在刪除路徑上執行(刪除沒有可供驗證的記錄載荷),它一直是靜默空操作 —— 刪除守衛請用 `beforeDelete` Hook | -| `priority` | 否 | 0–9999,數字越小越先執行(預設 100) | -| `active` | 否 | 不刪除即可開關(預設 `true`) | - -### 六種規則型別 - -| 型別 | 檢查什麼 | 關鍵配置 | -|---|---|---| -| `script` | 任意 CEL 謂詞 | `condition`(TRUE = 無效) | -| `state_machine` | 允許的狀態遷移 | `field`、`transitions` 對映、可選 `initialStates` | -| `format` | 單欄位匹配正則或內建格式 | `field`、`regex` 或 `format: 'email' \| 'url' \| 'phone' \| 'json'` | -| `cross_field` | 欄位之間的關係 | `fields`、`condition` | -| `json_schema` | JSON 欄位匹配 JSON Schema | `field`、`schema` | -| `conditional` | 僅在謂詞成立時應用巢狀規則 | `when`、`then`、可選 `otherwise` | - -兩條你會反覆用到的規則: - -```ts -// 状态机 —— 强制状态流转 -{ - name: 'order_status_transitions', - type: 'state_machine', - severity: 'error', - message: 'Invalid status transition', - field: 'status', - transitions: { - draft: ['submitted', 'cancelled'], - submitted: ['approved', 'rejected', 'cancelled'], - approved: ['completed'], - rejected: ['draft'], - cancelled: [], - completed: [], - }, - initialStates: ['draft'], // 16.0 新增:记录允许被“创建”为哪些状态 - events: ['insert', 'update'], // 包含 'insert' 才会检查 initialStates -} - -// 跨字段 —— 日期先后有序 -{ - name: 'date_range_valid', - type: 'cross_field', - severity: 'error', - message: 'End date must be after start date', - fields: ['start_date', 'end_date'], - condition: 'record.end_date <= record.start_date', - events: ['insert', 'update'], -} -``` - -**16.0 新增:**`state_machine` 規則可以宣告 `initialStates` —— 記錄允許被*建立*為哪些狀態。插入時狀態欄位的值不在列表內會被拒絕(`invalid_initial_state`)。`transitions` 只約束更新,而 `select` 欄位允許任何已宣告的選項作為初始值,所以不加 `initialStates` 時記錄可能"生在流程中間"(比如一建立就是 `approved`)。省略它則保持舊行為(插入時不檢查初始狀態)。規則的 `events` 必須包含 `insert`,該檢查才會執行。 - -在更新時的條件裡,`previous` 持有變更前的快照 —— `record.stage != previous.stage` 可檢測到變更。 - -## 唯一性:用索引,不用規則 - -**沒有** `uniqueness` 驗證型別 —— 這是有意為之。先 SELECT 再 INSERT 的檢查天然有競態(TOCTOU);資料庫唯一約束沒有。唯一性要在資料層強制: - -```ts -// 字段级 -email: Field.email({ label: 'Contact Email', unique: true }), - -// 组合 / 限定范围的唯一性用索引 -indexes: [ - { fields: ['code', 'organization'], unique: true }, - // 需要限定范围 / 条件约束时加 `partial` -] -``` - -同樣的邏輯也排除了非同步 / 遠端驗證(那是客戶端表單的關注點,放在寫路徑上還是 SSRF 與延遲隱患)和自定義處理器 —— 任意驗證程式碼應該放進 `beforeInsert` / `beforeUpdate` 生命週期 Hook,刪除時的守衛則放進 `beforeDelete` Hook。 - -## 自定義錯誤訊息 - -`message` 就是規則觸發時使用者看到的內容 —— 出現在 Console 表單和 API 錯誤響應中。要寫得可執行: - -| 弱 | 強 | -|---|---| -| "Invalid input" | "Close date is required for closed deals" | -| "Validation failed" | "Phone must match format: +1-XXX-XXX-XXXX" | -| "Error" | "Discount cannot exceed total" | - -用 `severity` 來校準:`error` 阻止儲存,`warning` 顯示訊息但放行儲存,`info` 純提示。 - -> **提示:**無法求值的謂詞(解析錯誤、未繫結變數)會被當作損壞的規則處理 —— 記錄日誌後跳過,而不是攔下每一次寫入。 - -## 最佳實踐 - -| 應該 | 不應該 | -|---|---| -| 在儘可能低的層做驗證(欄位 → 規則 → Hook) | 用 Hook 做欄位修飾符就能表達的事 | -| 寫清晰、可執行的訊息 | 在多層重複同一個檢查 | -| 用 `priority` 讓廉價的格式檢查先跑 | 構造過於複雜的條件 | -| 用真實資料測試規則 | 攔住正當的邊界情況 | - -## 下一步 - -| 頁面 | 原因 | -|---|---| -| [資料模型](/docs/build/data) | 這些規則所保護的物件與欄位 | -| [公式](/docs/build/data/formulas) | `condition` 與 `requiredWhen` 背後的 CEL 語言 | -| [關係](/docs/build/data/relationships) | 跨物件的引用完整性 | -| [CEL 參考](/docs/reference/cel) | 條件可用的完整運算子與標準庫參考 | -| [欄位型別](/docs/reference/field-types) | 各欄位型別的內建驗證預設值 | -| [流程](/docs/build/automation/flows) | 用自動化響應(合法的)記錄變更 | diff --git a/content/docs/configure/email.de.mdx b/content/docs/configure/email.de.mdx deleted file mode 100644 index a23bfdf..0000000 --- a/content/docs/configure/email.de.mdx +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: E-Mail -description: Konfigurieren Sie Anbieter und Vorlagen für die Zustellung von Transaktions-E-Mails. -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -ObjectOS versendet Transaktions-E-Mails über das E-Mail-Plugin des -Frameworks, wenn die Anwendung dies erfordert (Passwort-Zurücksetzung, -Einladungen, Genehmigungsbenachrichtigungen, geplante Berichte). Das -Plugin wird mit drei Transports ausgeliefert. - -## Transports - -| Anbieter | Verwenden, wenn | Erforderliche Umgebungsvariable | -|---|---|---| -| `log` | Lokale Entwicklung; protokolliert die E-Mail nach stdout, anstatt sie zu versenden | keine | -| `resend` | SaaS-Zustellbarkeit über Resend | `OS_EMAIL_API_KEY` | -| `postmark` | SaaS-Zustellbarkeit über Postmark | `OS_EMAIL_API_KEY` | - -Der Standardwert ist `log`. ObjectOS greift auf den Log-Transport zurück, -wenn ein echter Anbieter konfiguriert ist, aber kein API-Schlüssel -angegeben wurde — nützlich, um zu verhindern, dass Nicht-Produktions- -umgebungen versehentlich E-Mails versenden. - -### Was ist mit SMTP? - -Ein nativer SMTP-Transport ist derzeit **nicht** in die Laufzeit -integriert. Falls Ihre Umgebung SMTP erfordert (Unternehmens-Relay, -On-Prem-Mail, Air-Gapped-Bereitstellung), haben Sie zwei -produktionsreife Optionen: - -1. **Betreiben Sie ein SMTP-zu-API-Relay** vor ObjectOS. Resend, - Postmark und selbst gehostete Alternativen (Postal, Cuttlefish) - akzeptieren alle SMTP-Eingang und geben über ihre HTTP-API weiter — - ObjectOS kommuniziert wie gewohnt über HTTPS mit ihnen. -2. **Betreiben Sie die Laufzeit mit einem benutzerdefinierten - E-Mail-Plugin.** Die API des E-Mail-Plugins ist klein (eine einzige - `send(message)`-Funktion); ein Projekt-Plugin, das `nodemailer` - umschließt, wird über die `requires`-Liste eingebunden. Siehe den - [Leitfaden zur Plugin-Entwicklung](https://github.com/objectstack-ai/spec) - im Spec-Repository. - -Ein nativer SMTP-Transport ist in der Roadmap geplant; verfolgen Sie den -Fortschritt unter -[github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues). - -## Umgebungsvariablen - -| Variable | Zweck | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`, `resend` oder `postmark` | -| `OS_EMAIL_API_KEY` | API-Schlüssel des Anbieters (Resend oder Postmark) | -| `OS_EMAIL_FROM` | Standard-Absenderadresse. Unterstützt sowohl das Format `addr@x` als auch `Name ` | -| `OS_EMAIL_RETRIES` | Anzahl der Transport-Wiederholungsversuche bei Sendefehlern (Standard `0`) | - -Umgebungsvariablen überschreiben übereinstimmende Werte im -`email`-Konfigurationsblock des Artefakts, sodass Betreiber die -Zustellung umleiten können, ohne das Artefakt neu zu erstellen. - -## Vorlagen - -Wiederverwendbare Vorlagen werden in `sys_email_template` gespeichert. -Vorlagen unterstützen die Variableninterpolation, die von der -Template-Engine des Frameworks ausgewertet wird. Anwendungscode (oder -ein Flow) ruft den E-Mail-Dienst mit einer Vorlagen-ID und einem -Variablenbündel auf; der Dienst materialisiert Betreff/Inhalt und -übergibt ihn an den konfigurierten Transport. - -## Zustellung überprüfen - -Stellen Sie für Resend / Postmark sicher, dass die sendende Domain im -Dashboard des Anbieters konfiguriert ist (SPF, DKIM, optional DMARC). -Die schnellste End-to-End-Prüfung ist die Aktion **Test-E-Mail senden** -in der Console auf der Seite der E-Mail-Einstellungen — sie verwendet den -Live-Transport und zeigt Transportfehler direkt an. - -## Betriebshinweise - -- Behandeln Sie den API-Schlüssel als Geheimnis. Speichern Sie ihn im - Secret-Manager des Kunden, niemals im Artefakt. -- Beobachten Sie die Transportfehler-Protokolle: Ratenbegrenzungen des - Anbieters, Unterdrückungen und Bounces erscheinen alle dort. -- Audit-relevante Transaktions-E-Mails (Passwort-Zurücksetzung, - MFA-Challenge) sollten gemäß der Richtlinie des Kunden aufbewahrt - werden — legen Sie die Aufbewahrung im Audit-Log fest, nicht im - Transport. -- Ausgehende E-Mails blockieren keine Geschäftstransaktionen: - Sendefehler werden als Fehler angezeigt, machen aber die ursprüngliche - Datensatzänderung nicht rückgängig. diff --git a/content/docs/configure/email.es.mdx b/content/docs/configure/email.es.mdx deleted file mode 100644 index 99ff219..0000000 --- a/content/docs/configure/email.es.mdx +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Email -description: Configura proveedores de entrega de correo transaccional y plantillas. -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -ObjectOS envía correo transaccional a través del plugin de email del framework -cuando la aplicación lo requiere (restablecimiento de contraseña, invitaciones, -notificaciones de aprobación, informes programados). El plugin incluye tres -transportes. - -## Transportes - -| Proveedor | Cuándo usarlo | Variables de entorno requeridas | -|---|---|---| -| `log` | Desarrollo local; registra el correo en stdout en lugar de enviarlo | ninguna | -| `resend` | Capacidad de entrega SaaS mediante Resend | `OS_EMAIL_API_KEY` | -| `postmark` | Capacidad de entrega SaaS mediante Postmark | `OS_EMAIL_API_KEY` | - -El valor por defecto es `log`. ObjectOS recurre al transporte log cuando se -configura un proveedor real pero no se suministra una clave de API — útil para -evitar que los entornos no productivos envíen correo de forma accidental. - -### ¿Qué pasa con SMTP? - -El transporte SMTP nativo **no** está integrado en el runtime actualmente. Si tu -entorno requiere SMTP (relay corporativo, correo on-premise, despliegue aislado) -tienes dos opciones de nivel producción: - -1. **Ejecuta un relay de SMTP a API** delante de ObjectOS. Resend, Postmark - y las alternativas autoalojadas (Postal, Cuttlefish) aceptan ingreso por - SMTP y reenvían a través de su API HTTP — ObjectOS se comunica con ellas - sobre HTTPS de forma normal. -2. **Ejecuta el runtime con un plugin de email personalizado.** La API del - plugin de email es pequeña (una sola función `send(message)`); un plugin del - proyecto que envuelva `nodemailer` se conecta a través de la lista `requires`. - Consulta la [guía de creación de plugins](https://github.com/objectstack-ai/spec) - en el repositorio de la especificación. - -El transporte SMTP nativo está en la hoja de ruta; sigue el progreso en -[github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues). - -## Variables de entorno - -| Variable | Propósito | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`, `resend` o `postmark` | -| `OS_EMAIL_API_KEY` | Clave de API del proveedor (Resend o Postmark) | -| `OS_EMAIL_FROM` | Dirección de remitente por defecto. Admite los formatos `addr@x` y `Name ` | -| `OS_EMAIL_RETRIES` | Número de reintentos del transporte ante un fallo de envío (por defecto `0`) | - -Las variables de entorno anulan los valores correspondientes del bloque de -configuración `email` del artefacto, de modo que los operadores pueden -redirigir la entrega sin reconstruir el artefacto. - -## Plantillas - -Las plantillas reutilizables residen en `sys_email_template`. Las plantillas -admiten interpolación de variables evaluada por el motor de plantillas del -framework. El código de la aplicación (o un flujo) invoca el servicio de email -con un id de plantilla y un conjunto de variables; el servicio materializa el -asunto/cuerpo y lo entrega al transporte configurado. - -## Verificación de la entrega - -Para Resend / Postmark, verifica que el dominio de envío esté configurado en el -panel del proveedor (SPF, DKIM y, opcionalmente, DMARC). La comprobación -extremo a extremo más rápida es la acción **Send test email** de la Console en la -página de configuración de email — utiliza el transporte real y muestra los -errores de transporte en línea. - -## Recomendaciones operativas - -- Trata la clave de API como un secreto. Almacénala en el gestor de secretos del - cliente, nunca en el artefacto. -- Vigila los registros de errores de transporte: los límites de tasa del - proveedor, las supresiones y los rebotes aparecen todos ahí. -- El correo transaccional sensible para auditoría (restablecimiento de - contraseña, desafío MFA) debe conservarse de acuerdo con la política del - cliente — establece la retención en el registro de auditoría, no en el - transporte. -- El correo saliente no bloquea las transacciones de negocio: los fallos de - envío se muestran como errores pero no revierten el cambio del registro que - los originó. diff --git a/content/docs/configure/email.fr.mdx b/content/docs/configure/email.fr.mdx deleted file mode 100644 index ed38e6f..0000000 --- a/content/docs/configure/email.fr.mdx +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Email -description: Configurez les fournisseurs et les modèles de livraison d'e-mails transactionnels. -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -ObjectOS envoie des e-mails transactionnels via le plugin email du -framework lorsque l'application le requiert (réinitialisation de mot de -passe, invitations, notifications d'approbation, rapports planifiés). Le -plugin est fourni avec trois transports. - -## Transports - -| Fournisseur | À utiliser quand | Variables d'env requises | -|---|---|---| -| `log` | Développement local ; consigne l'e-mail dans stdout au lieu de l'envoyer | aucune | -| `resend` | Délivrabilité SaaS via Resend | `OS_EMAIL_API_KEY` | -| `postmark` | Délivrabilité SaaS via Postmark | `OS_EMAIL_API_KEY` | - -La valeur par défaut est `log`. ObjectOS bascule vers le transport log -lorsqu'un véritable fournisseur est configuré mais qu'aucune clé API -n'est fournie — utile pour empêcher les environnements non productifs -d'envoyer accidentellement des e-mails. - -### Qu'en est-il de SMTP ? - -Le transport SMTP natif n'est **pas** intégré au runtime aujourd'hui. Si -votre environnement requiert SMTP (relais d'entreprise, messagerie -on-prem, déploiement isolé du réseau), vous disposez de deux options de -qualité production : - -1. **Exécutez un relais SMTP-vers-API** devant ObjectOS. Resend, - Postmark et les alternatives auto-hébergées (Postal, Cuttlefish) - acceptent tous l'entrée SMTP et la réémettent via leur API HTTP — - ObjectOS communique avec eux en HTTPS comme d'habitude. -2. **Exécutez le runtime avec un plugin email personnalisé.** L'API du - plugin email est réduite (une seule fonction `send(message)`) ; un - plugin de projet qui encapsule `nodemailer` s'intègre via la liste - `requires`. Consultez le - [guide de création de plugins](https://github.com/objectstack-ai/spec) - dans le dépôt spec. - -Le transport SMTP natif figure sur la feuille de route ; suivez -l'avancement sur -[github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues). - -## Variables d'environnement - -| Variable | Rôle | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`, `resend` ou `postmark` | -| `OS_EMAIL_API_KEY` | Clé API du fournisseur (Resend ou Postmark) | -| `OS_EMAIL_FROM` | Adresse d'expéditeur par défaut. Prend en charge les formats `addr@x` et `Name ` | -| `OS_EMAIL_RETRIES` | Nombre de tentatives de réessai du transport en cas d'échec d'envoi (par défaut `0`) | - -Les variables d'environnement remplacent les valeurs correspondantes du -bloc de configuration `email` de l'artefact, ce qui permet aux opérateurs -de rediriger la livraison sans reconstruire l'artefact. - -## Modèles - -Les modèles réutilisables résident dans `sys_email_template`. Les modèles -prennent en charge l'interpolation de variables évaluée par le moteur de -modèles du framework. Le code de l'application (ou un flow) appelle le -service email avec un identifiant de modèle et un ensemble de variables ; -le service matérialise le sujet/corps et le transmet au transport -configuré. - -## Vérifier la livraison - -Pour Resend / Postmark, vérifiez que le domaine d'envoi est configuré -dans le tableau de bord du fournisseur (SPF, DKIM, et éventuellement -DMARC). La vérification de bout en bout la plus rapide est l'action -**Send test email** de la Console sur la page des paramètres email — elle -utilise le transport en direct et fait remonter les erreurs de transport -de manière intégrée. - -## Conseils opérationnels - -- Traitez la clé API comme un secret. Stockez-la dans le gestionnaire de - secrets du client, jamais dans l'artefact. -- Surveillez les journaux d'erreurs de transport : les limites de débit - des fournisseurs, les suppressions et les rebonds y apparaissent tous. -- Les e-mails transactionnels sensibles aux audits (réinitialisation de - mot de passe, défi MFA) doivent être conservés conformément à la - politique du client — définissez la rétention sur le journal d'audit, - pas sur le transport. -- Les e-mails sortants ne bloquent pas les transactions métier : les - échecs d'envoi sont signalés comme des erreurs mais n'annulent pas la - modification de l'enregistrement à l'origine. diff --git a/content/docs/configure/email.ja.mdx b/content/docs/configure/email.ja.mdx deleted file mode 100644 index 666ec61..0000000 --- a/content/docs/configure/email.ja.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: メール -description: トランザクションメールの配信プロバイダーとテンプレートを設定します。 -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -ObjectOS は、アプリケーションが必要とする場面(パスワードリセット、招待、承認通知、スケジュールされたレポート)で、フレームワークのメールプラグインを通じてトランザクションメールを送信します。このプラグインには 3 つのトランスポートが同梱されています。 - -## トランスポート - -| プロバイダー | 使用する場面 | 必要な環境変数 | -|---|---|---| -| `log` | ローカル開発。メールを送信する代わりに stdout に出力します | なし | -| `resend` | Resend による SaaS 配信 | `OS_EMAIL_API_KEY` | -| `postmark` | Postmark による SaaS 配信 | `OS_EMAIL_API_KEY` | - -デフォルトは `log` です。実際のプロバイダーが設定されていても API キーが指定されていない場合、ObjectOS は log トランスポートにフォールバックします。これは非本番環境が誤ってメールを送信してしまうのを防ぐのに役立ちます。 - -### SMTP については? - -ネイティブの SMTP トランスポートは、現時点ではランタイムに**組み込まれていません**。環境で SMTP が必要な場合(企業のリレー、オンプレミスのメール、エアギャップ環境へのデプロイ)には、本番グレードの選択肢が 2 つあります。 - -1. **SMTP-to-API リレーを ObjectOS の前段で実行する。** Resend、Postmark、およびセルフホスト型の代替手段(Postal、Cuttlefish)はいずれも SMTP の受信を受け付け、HTTP API 経由で再送信します。ObjectOS は通常どおり HTTPS でこれらと通信します。 -2. **カスタムメールプラグインでランタイムを実行する。** メールプラグインの API は小さく(`send(message)` 関数が 1 つ)、`nodemailer` をラップするプロジェクトプラグインを `requires` リスト経由で組み込めます。spec リポジトリの[プラグイン作成ガイド](https://github.com/objectstack-ai/spec)を参照してください。 - -ネイティブの SMTP トランスポートはロードマップに含まれています。進捗は [github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues) で確認できます。 - -## 環境変数 - -| 変数 | 目的 | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`、`resend`、または `postmark` | -| `OS_EMAIL_API_KEY` | プロバイダーの API キー(Resend または Postmark) | -| `OS_EMAIL_FROM` | デフォルトの送信元アドレス。`addr@x` と `Name ` の両方の形式をサポートします | -| `OS_EMAIL_RETRIES` | 送信失敗時のトランスポート再試行回数(デフォルトは `0`) | - -環境変数はアーティファクトの `email` 設定ブロック内の対応する値を上書きするため、運用担当者はアーティファクトを再ビルドすることなく配信先を変更できます。 - -## テンプレート - -再利用可能なテンプレートは `sys_email_template` に格納されます。テンプレートは、フレームワークのテンプレートエンジンによって評価される変数の補間をサポートします。アプリケーションコード(またはフロー)は、テンプレート ID と変数バンドルを指定してメールサービスを呼び出します。サービスは件名と本文を生成し、設定されたトランスポートに引き渡します。 - -## 配信の検証 - -Resend / Postmark の場合は、送信ドメインがプロバイダーのダッシュボードで設定されていること(SPF、DKIM、必要に応じて DMARC)を確認してください。最も手早いエンドツーエンドの確認方法は、メール設定ページにある Console の**テストメール送信**アクションです。これはライブのトランスポートを使用し、トランスポートエラーをインラインで表示します。 - -## 運用上のガイダンス - -- API キーはシークレットとして扱ってください。顧客のシークレットマネージャーに保存し、アーティファクトには決して含めないでください。 -- トランスポートエラーのログを監視してください。プロバイダーのレート制限、抑制(suppression)、バウンスはすべてそこに表示されます。 -- 監査上重要なトランザクションメール(パスワードリセット、MFA チャレンジ)は、顧客のポリシーに従って保持する必要があります。保持期間はトランスポートではなく監査ログに設定してください。 -- 送信メールは業務トランザクションをブロックしません。送信失敗はエラーとして表示されますが、発端となったレコードの変更をロールバックすることはありません。 diff --git a/content/docs/configure/email.ko.mdx b/content/docs/configure/email.ko.mdx deleted file mode 100644 index f446276..0000000 --- a/content/docs/configure/email.ko.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: 이메일 -description: 트랜잭션 이메일 전송 제공자와 템플릿을 구성합니다. -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -ObjectOS는 애플리케이션에서 필요할 때(비밀번호 재설정, 초대, 승인 -알림, 예약 보고서) 프레임워크의 이메일 플러그인을 통해 트랜잭션 -이메일을 전송합니다. 플러그인에는 세 가지 전송 방식이 함께 제공됩니다. - -## 전송 방식 - -| 제공자 | 사용 시점 | 필요한 env | -|---|---|---| -| `log` | 로컬 개발용. 이메일을 전송하지 않고 stdout에 로깅합니다 | 없음 | -| `resend` | Resend를 통한 SaaS 전달성 | `OS_EMAIL_API_KEY` | -| `postmark` | Postmark를 통한 SaaS 전달성 | `OS_EMAIL_API_KEY` | - -기본값은 `log`입니다. 실제 제공자가 구성되어 있지만 API 키가 제공되지 -않은 경우 ObjectOS는 log 전송 방식으로 폴백합니다. 이는 비프로덕션 -환경에서 실수로 메일이 전송되는 것을 방지하는 데 유용합니다. - -### SMTP는 어떤가요? - -네이티브 SMTP 전송 방식은 현재 런타임에 **내장되어 있지 않습니다**. -환경에서 SMTP가 필요한 경우(기업 릴레이, 온프레미스 메일, 에어갭 -배포) 두 가지 프로덕션급 옵션이 있습니다. - -1. **SMTP-to-API 릴레이를 ObjectOS 앞단에서 실행합니다.** Resend, - Postmark, 그리고 자체 호스팅 대안(Postal, Cuttlefish)은 모두 SMTP - 인그레스를 수용하고 자체 HTTP API를 통해 재전송합니다. ObjectOS는 - 평소처럼 HTTPS로 이들과 통신합니다. -2. **사용자 정의 이메일 플러그인으로 런타임을 실행합니다.** 이메일 - 플러그인 API는 작습니다(하나의 `send(message)` 함수). `nodemailer`를 - 래핑하는 프로젝트 플러그인은 `requires` 목록을 통해 연결됩니다. - spec 저장소의 [플러그인 작성 가이드](https://github.com/objectstack-ai/spec)를 - 참고하세요. - -네이티브 SMTP 전송 방식은 로드맵에 있습니다. 진행 상황은 -[github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues)에서 -확인하세요. - -## 환경 변수 - -| 변수 | 용도 | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`, `resend`, 또는 `postmark` | -| `OS_EMAIL_API_KEY` | 제공자 API 키(Resend 또는 Postmark) | -| `OS_EMAIL_FROM` | 기본 발신 주소. `addr@x`와 `Name ` 형식을 모두 지원합니다 | -| `OS_EMAIL_RETRIES` | 전송 실패 시 전송 재시도 횟수(기본값 `0`) | - -환경 변수는 아티팩트의 `email` 구성 블록에서 일치하는 값을 -재정의하므로, 운영자는 아티팩트를 다시 빌드하지 않고도 전송을 다시 -지정할 수 있습니다. - -## 템플릿 - -재사용 가능한 템플릿은 `sys_email_template`에 저장됩니다. 템플릿은 -프레임워크의 템플릿 엔진이 평가하는 변수 보간을 지원합니다. -애플리케이션 코드(또는 플로우)는 템플릿 id와 변수 번들로 이메일 -서비스를 호출합니다. 서비스는 제목/본문을 구체화하여 구성된 전송 -방식에 전달합니다. - -## 전달 확인 - -Resend / Postmark의 경우, 발신 도메인이 제공자 대시보드에 구성되어 -있는지(SPF, DKIM, 선택적으로 DMARC) 확인하세요. 가장 빠른 엔드투엔드 -점검 방법은 이메일 설정 페이지의 Console **Send test email** 작업입니다. -이는 실제 전송 방식을 사용하며 전송 오류를 인라인으로 표시합니다. - -## 운영 지침 - -- API 키를 비밀로 취급하세요. 아티팩트가 아닌 고객의 시크릿 매니저에 - 저장하세요. -- 전송 오류 로그를 주시하세요. 제공자 속도 제한, 차단(suppression), - 반송(bounce)이 모두 여기에 표시됩니다. -- 감사에 민감한 트랜잭션 메일(비밀번호 재설정, MFA 챌린지)은 고객의 - 정책에 따라 보존해야 합니다. 보존 기간은 전송 방식이 아닌 감사 - 로그에 설정하세요. -- 발신 메일은 비즈니스 트랜잭션을 차단하지 않습니다. 전송 실패는 - 오류로 표시되지만, 원본 레코드 변경을 롤백하지는 않습니다. diff --git a/content/docs/configure/email.mdx b/content/docs/configure/email.mdx index 6369868..02f3990 100644 --- a/content/docs/configure/email.mdx +++ b/content/docs/configure/email.mdx @@ -6,8 +6,9 @@ description: Configure transactional email delivery providers and templates. ObjectOS sends transactional email through the framework's email plugin when the application requires it (password reset, invitations, -approval notifications, scheduled reports). The plugin ships with three -transports. +approval notifications). There are no scheduled-report emails: ObjectStack +17.5 removed scheduled report delivery, with no replacement. The plugin +ships with three transports. ## Transports diff --git a/content/docs/configure/email.zh-Hans.mdx b/content/docs/configure/email.zh-Hans.mdx deleted file mode 100644 index ecc50f6..0000000 --- a/content/docs/configure/email.zh-Hans.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: 邮件 -description: 配置事务性邮件投递的 Provider 和模板。 -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -当应用需要时(密码重置、邀请、审批通知、定时报告),ObjectOS 通过框架的邮件插件发送事务性邮件。该插件内置三种传输方式。 - -## 传输方式 - -| Provider | 适用场景 | 必需环境变量 | -|---|---|---| -| `log` | 本地开发;将邮件打印到 stdout 而不实际发送 | 无 | -| `resend` | 通过 Resend 实现 SaaS 投递 | `OS_EMAIL_API_KEY` | -| `postmark` | 通过 Postmark 实现 SaaS 投递 | `OS_EMAIL_API_KEY` | - -默认是 `log`。当配置了真实 Provider 但未提供 API Key 时,ObjectOS 会回退到 log 传输——便于让非生产环境不会意外发出邮件。 - -### 关于 SMTP - -运行时目前**未**内置原生 SMTP 传输。如果你的环境必须使用 SMTP(企业中继、本地邮件、内网/离线部署),有两种生产级方案: - -1. **在 ObjectOS 前部署 SMTP-to-API 中继。** Resend、Postmark 以及自托管替代品(Postal、Cuttlefish)都能接收 SMTP 并通过其 HTTP API 重新投递 —— ObjectOS 仍照常用 HTTPS 与它们交互。 -2. **以自定义邮件插件运行运行时。** 邮件插件 API 很小(一个 `send(message)` 函数);在项目插件中包装 `nodemailer`,通过 `requires` 列表接入。参见 spec 仓库中的 [插件开发指南](https://github.com/objectstack-ai/spec)。 - -原生 SMTP 传输在 roadmap 中;进展见 [github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues)。 - -## 环境变量 - -| 变量 | 用途 | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`、`resend` 或 `postmark` | -| `OS_EMAIL_API_KEY` | Provider API Key(Resend 或 Postmark) | -| `OS_EMAIL_FROM` | 默认发件人。支持 `addr@x` 和 `Name ` 两种格式 | -| `OS_EMAIL_RETRIES` | 发送失败的传输重试次数(默认 `0`) | - -环境变量优先于 artifact 中 `email` 配置块的同名值,因此运维可以无需重建 artifact 即可重定向投递。 - -## 模板 - -可复用模板存放在 `sys_email_template`。模板支持框架模板引擎评估的变量插值。应用代码(或流程)以模板 id 和变量包调用邮件服务;服务渲染主题/正文并交给配置的传输方式。 - -## 验证投递 - -对 Resend / Postmark,验证发送域名已在 Provider 控制台中配置(SPF、DKIM,可选 DMARC)。最快的端到端检查是邮件设置页的 **Send test email** 操作——它使用真实传输方式并把传输错误就地呈现。 - -## 运维建议 - -- 把 API Key 当作 secret。存于客户的 secret manager,永不放入 artifact。 -- 关注传输错误日志:Provider 限流、抑制名单与退信都会出现在那里。 -- 对审计敏感的事务邮件(密码重置、MFA 挑战)应按客户策略保留——在审计日志上设置保留期,而不是在传输层。 -- 出站邮件不阻塞业务事务:发送失败会被报告为错误,但不会回滚触发邮件的记录变更。 diff --git a/content/docs/configure/email.zh-Hant.mdx b/content/docs/configure/email.zh-Hant.mdx deleted file mode 100644 index 374156c..0000000 --- a/content/docs/configure/email.zh-Hant.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant -title: 郵件 -description: 配置事務性郵件投遞的 Provider 和模板。 -translation: - source_sha: cdcc3140f80c8ff398899d136753a2c39e75ccc539537518272da44f0bee4bc2 - guide_rev: 1 - mode: auto ---- - -當應用需要時(密碼重置、邀請、審批通知、定時報告),ObjectOS 通過框架的郵件外掛傳送事務性郵件。該外掛內建三種傳輸方式。 - -## 傳輸方式 - -| Provider | 適用場景 | 必需環境變數 | -|---|---|---| -| `log` | 本地開發;將郵件列印到 stdout 而不實際傳送 | 無 | -| `resend` | 通過 Resend 實現 SaaS 投遞 | `OS_EMAIL_API_KEY` | -| `postmark` | 通過 Postmark 實現 SaaS 投遞 | `OS_EMAIL_API_KEY` | - -預設是 `log`。當配置了真實 Provider 但未提供 API Key 時,ObjectOS 會回退到 log 傳輸——便於讓非生產環境不會意外發出郵件。 - -### 關於 SMTP - -執行時目前**未**內建原生 SMTP 傳輸。如果你的環境必須使用 SMTP(企業中繼、本地郵件、內網/離線部署),有兩種生產級方案: - -1. **在 ObjectOS 前部署 SMTP-to-API 中繼。** Resend、Postmark 以及自託管替代品(Postal、Cuttlefish)都能接收 SMTP 並通過其 HTTP API 重新投遞 —— ObjectOS 仍照常用 HTTPS 與它們互動。 -2. **以自定義郵件外掛執行執行時。** 郵件外掛 API 很小(一個 `send(message)` 函式);在專案外掛中包裝 `nodemailer`,通過 `requires` 列表接入。參見 spec 倉庫中的 [外掛開發指南](https://github.com/objectstack-ai/spec)。 - -原生 SMTP 傳輸在 roadmap 中;進展見 [github.com/objectstack-ai/objectos/issues](https://github.com/objectstack-ai/objectos/issues)。 - -## 環境變數 - -| 變數 | 用途 | -|---|---| -| `OS_EMAIL_PROVIDER` | `log`、`resend` 或 `postmark` | -| `OS_EMAIL_API_KEY` | Provider API Key(Resend 或 Postmark) | -| `OS_EMAIL_FROM` | 預設發件人。支援 `addr@x` 和 `Name ` 兩種格式 | -| `OS_EMAIL_RETRIES` | 傳送失敗的傳輸重試次數(預設 `0`) | - -環境變數優先於 artifact 中 `email` 配置塊的同名值,因此運維可以無需重建 artifact 即可重定向投遞。 - -## 模板 - -可複用模板存放在 `sys_email_template`。模板支援框架模板引擎評估的變數插值。應用程式碼(或流程)以模板 id 和變數包呼叫郵件服務;服務渲染主題/正文並交給配置的傳輸方式。 - -## 驗證投遞 - -對 Resend / Postmark,驗證傳送域名已在 Provider 控制台中配置(SPF、DKIM,可選 DMARC)。最快的端到端檢查是郵件設定頁的 **Send test email** 操作——它使用真實傳輸方式並把傳輸錯誤就地呈現。 - -## 運維建議 - -- 把 API Key 當作 secret。存於客戶的 secret manager,永不放入 artifact。 -- 關注傳輸錯誤日誌:Provider 限流、抑制名單與退信都會出現在那裡。 -- 對審計敏感的事務郵件(密碼重置、MFA 挑戰)應按客戶策略保留——在審計日誌上設定保留期,而不是在傳輸層。 -- 出站郵件不阻塞業務事務:傳送失敗會被報告為錯誤,但不會回滾觸發郵件的記錄變更。 diff --git a/content/docs/configure/localization.mdx b/content/docs/configure/localization.mdx index b03877e..1535b78 100644 --- a/content/docs/configure/localization.mdx +++ b/content/docs/configure/localization.mdx @@ -1,7 +1,7 @@ --- title: Localization seoTitle: "Localization: Timezone, Language and Formats" -description: Set the tenant-wide timezone, language, country, and date, time, and number formats that every rendered value inherits. +description: Set the tenant-wide timezone, language, country, default currency and fiscal year start that every rendered value inherits. --- Localization sets the **tenant-level defaults** for how ObjectOS interprets @@ -11,7 +11,7 @@ override their own language in their [Account](/docs/use/profile), but the defaults you set here apply to everyone who has not. Open **Setup → Configuration → Localization**. The page is split into two -sections: **Region** and **Formats**. +sections: **Region** and **Finance**. ## Region @@ -34,28 +34,35 @@ the deployment reports as its home. message catalog and formatting rules the UI falls back to. `Default country` is a free-text field that expects a two-letter ISO code. -## Formats +## Finance -Formats control *presentation* — how an already-resolved value is written out. -They do not change stored data, only how it is displayed. - -| Setting | Default | Example | +| Setting | Default | Drives | |---|---|---| -| `Date format` | ISO | `2026-06-17` | -| `Time format` | 24-hour | `14:30` | -| `Number format` | comma / dot | `1,234.56` | -| `Currency` | — | Currency symbol and placement for money fields | +| `Default currency` | none | The ISO 4217 code a currency field uses when it does not declare its own. Leave it unset and such amounts render as plain numbers, with no guessed symbol | +| `Fiscal year start` | `January` | The first month of the fiscal year — what "this quarter" and "this fiscal year" mean in reports | + +## Dates, times and numbers follow the language -Each is a dropdown. Pick the presentation your users expect; the underlying -timestamps and numbers are stored in a canonical form and only formatted at -render time. +There is no separate format setting. How a date, a time or a number is +written out, and which day a week starts on, follow `Default language` +(or the user's own language). ObjectStack 17.7 retired the four format +settings this page used to list — `date_format`, `time_format`, +`number_format` and `first_day_of_week` — because nothing read them. A +value stored earlier is kept but no longer served; a +`PUT /api/settings/localization` that names one of them is refused +`400 UNKNOWN_KEY` for the whole request, the valid keys beside it +included; and the `OS_LOCALIZATION_*_FORMAT` and +`OS_LOCALIZATION_FIRST_DAY_OF_WEEK` variables are no longer read. Formats +change only how a value is displayed: timestamps and numbers are stored +in a canonical form either way. ## Tenant defaults vs. user overrides Everything on this page is a **tenant default**. When a user sets their own language in **Account**, that choice wins for that user's session and rendered UI; users who leave their preference unset inherit the `Default language` you -configure here. Timezone, country, and format defaults apply tenant-wide. +configure here. Timezone, country, currency and fiscal-year defaults apply +tenant-wide. ## Where to go next diff --git a/content/docs/operate/upgrade.mdx b/content/docs/operate/upgrade.mdx index 28ede08..113875e 100644 --- a/content/docs/operate/upgrade.mdx +++ b/content/docs/operate/upgrade.mdx @@ -29,7 +29,9 @@ a checklist. Keep the **previous** digest: that is what a rollback is. To roll back, restore the previous digest and repeat step 3 — valid as long as the version you are leaving made no incompatible schema change (the release notes say -when it did). +when it did). Secrets have the same one-way edge: a secret set or rotated +on ObjectStack 17.7 or later cannot be read by a release before 17.7 (see +[17.7](#objectstack-177) below). ## On Kubernetes and air-gapped sites @@ -49,3 +51,201 @@ independently of the image — see published artifacts as immutable: if you overwrite one in place, you lose the ability to roll back cleanly even when the database backup is perfect ([Backup → Artifact versioning](/docs/operate/backup#artifact-versioning)). + +## Version-specific steps + +The steps above are the same for every release. Some ObjectStack releases +underneath ObjectOS also ask the operator to act: set a variable before the +new image boots, run a data migration, rotate a credential. The image's +[release manifest](/docs/deploy/docker#the-image-can-tell-you-what-it-is) +says which ObjectStack version it embeds. Work through the subsection of +every version you cross, oldest first, beside the steps above. + +Each subsection is the operator's share of that release's upgrade +checklist and links the whole checklist; what a release changes for the app +you run (its metadata and its code) is in the same checklist and in the +[Changelog](/docs/resources/changelog). The `os migrate` commands are the +ObjectStack CLI at the version the new image embeds. They boot the app to +read its metadata, so run them with the environment the deployment boots +with — the same database and the same app — see +[CLI → Database migrations](/docs/reference/cli#database-migrations--os-migrate). + +> **Rehearse on a copy.** The release notes mark most checklist lines +> *not exercised*: derived from the change, not from a walked upgrade. Run +> each step against a copy of the database first, and back up before the +> real one. + +### ObjectStack 17.0 + +- **Run the two data migrations.** `os migrate files-to-references`, then + again with `--apply`; and `os migrate value-shapes`, then `--apply` once + it reports nothing. Until each has recorded its flag, a database created + before 17.0 keeps accepting malformed file and reference values with a + warning; after, it refuses them. For an integration you cannot fix in + time, see the [escape hatches](/docs/reference/environment-variables#data-contract-escape-hatches). +- **Every datasource the app binds must connect.** One that cannot fails + the boot, and `/api/v1/ready` answers `503` while one stops answering. + +Full checklist: [17.0.0](https://objectstack.ai/docs/releases/v17/17-0#upgrade-checklist). + +### ObjectStack 17.1 + +- **Before upgrading, list the permission sets and positions switched + off** (`active: false`). From 17.1 they stop granting, and a sharing rule + that names a deactivated position has its shares revoked. Re-activate the + ones still meant to grant. +- **Re-mint API keys under `isolated`.** Keys minted before 17.1 carry no + organization and are refused under `OS_TENANCY_POSTURE=isolated`. +- **Export is granted per object.** The shipped administrator permission + sets no longer export every object; grant `allowExport` in the app's own + permission set where export is intended. + +Full checklist: [17.1.0](https://objectstack.ai/docs/releases/v17/17-1#upgrade-checklist). + +### ObjectStack 17.2 + +- **Re-point monitoring keyed on `http_request_errors_total`.** The series + is retired and reads a flat zero, which looks like a healthy server. Use + `http_requests_total{status=~"5.."}`. +- **Grant `manage_platform_settings` or `manage_metadata`** to any + credential that calls the external-datasource routes; they now refuse + other callers `403`. + +Full checklist: [17.2.0](https://objectstack.ai/docs/releases/v17/17-2#upgrade-checklist). + +### ObjectStack 17.3 + +- **Walled posture: set `OS_PLATFORM_OWNER_EMAIL` before the new image + boots**, or it refuses to start. See + [Environment Variables](/docs/reference/environment-variables#multi-organization-deployments). +- **Deduplicate auto-numbers before the new unique indexes can build.** Run + `os migrate duplicates`, fix the duplicates it reports, then re-run + `os migrate plan`. Until you do, the index is not built and the + uniqueness is not enforced; the boot only logs it, so a clean boot is not + evidence that the index exists. +- **Backfill `sys_record_share`.** Sharing grants written before 17.3 carry + no organization, and a tenant-scoped read stops finding them. ObjectStack + ships the backfill as two functions of `@objectstack/plugin-sharing` — + `planSysRecordShareOrganizationBackfill` (a dry run) and + `runSysRecordShareOrganizationBackfill` — not as an `os` command, and + nothing runs it at boot. +- **Open sign-up must now be declared.** An app that declares no audience + posture is invite-only: a self-registration is refused + `403 SELF_REGISTRATION_CLOSED` unless the address holds a pending + invitation (the first account on a fresh install is exempt). + +Full checklist: [17.3.0](https://objectstack.ai/docs/releases/v17/17-3#upgrade-checklist). + +### ObjectStack 17.4 + +- **Under `isolated`, re-mint API keys that carry no organization.** They + move from `200` to `401`. Under `group` such a key still works. +- **Expect `503` from the storage download and settings routes** while the + authorization store is unreachable, where they used to degrade quietly. + +Full checklist: [17.4.0](https://objectstack.ai/docs/releases/v17/17-4#upgrade-checklist). + +### ObjectStack 17.5 + +Before you upgrade: + +- **Walled posture: list every platform administrator in + `OS_PLATFORM_OWNER_EMAIL`** (comma-separated, verified addresses). An + `admin_full_access` grant stops conferring the standing, and a deployment + with no declaration comes up with no platform administrator. +- **Decide about scheduled work.** Time-triggered flows and packaged cron + jobs stop running unless the deployment sets + `OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true`. Under `isolated`, first give + each scheduled flow's start node the `organization` it runs as, and drain + its suspended runs. See [Flows](/docs/build/automation/flows#scheduled-run-on-a-clock). +- **Pre-flight the account-identity change.** Run `os migrate account-issuer` + (read-only; it exits non-zero while two accounts would collide) and + resolve every collision it reports: colliding rows are never merged for + you. + +Getting onto the release: + +```bash +os migrate account-issuer # expect: safe to drop +os migrate plan # read every destructive change first +os migrate apply --allow-destructive # drops sys_account.issuer, after a backup +os migrate account-issuer # again: expect zero collisions +``` + +`--allow-destructive` applies every destructive change the plan lists, not +only this one — which is why the plan comes first. + +After it: + +- An anonymous `GET /api/v1/auth/get-session` answers `401`, no longer + `200` with `null`. +- Analytics refuses `403` a caller who cannot read the object. +- Failed notification deliveries are kept 7 days, not 90. + +Full checklist: [17.5.0](https://objectstack.ai/docs/releases/v17/17-5#upgrade-checklist). + +### ObjectStack 17.6 + +- **Hold the upgrade if the app declares an anonymous endpoint** + (`authRequired: false`) that reads or writes objects. From 17.6 it is + refused every object, and no supported channel grants anonymous callers a + permission set, in 17.7 either. Public forms are a separate door and keep + working. +- **Grant `manage_platform_settings`** to every user or integration that + reads `datasource` or `external_catalog` metadata, or saves `datasource` + metadata, through `/api/v1/meta`. +- **Find stored flows that share a packaged flow's name** (the startup log + warns about each). From 17.6 the packaged flow wins and the stored one + stops running; clone the one you need under a new name. +- **Rewrite the copies of metadata bodies at rest:** + `os migrate audit-metadata-bodies`, then again with `--apply`. ⚠️ On + 17.6.0 even the dry run writes to the database — run it against a copy. +- **Find `datetime` values before the year 1000** and rewrite them; after + the upgrade, any write that carries one is refused. + +Full checklist: [17.6.0](https://objectstack.ai/docs/releases/v17/17-6#upgrade-checklist). + +### ObjectStack 17.7 + +Before you upgrade: + +- **Run any conversion that reads a retired field's leftover column** while + that field is still declared. After the upgrade no runtime door returns + the column; only `os migrate unmapped-columns --object NAME` reads it. +- **Add `sharing.enabled: true` to every public form that must stay + public**, in the app and in stored overlays. Without it the form answers + `404 FORM_NOT_FOUND`. +- **If you may need to roll back past 17.7, keep a way to set every secret + again.** A secret set, rotated or re-sealed on 17.7 is written in a format + an earlier release cannot open. +- **Replace an in-memory boot store** (`OS_DATABASE_DRIVER=memory`, or a + `memory://` URL): it is refused. + +Getting onto the release: run `os migrate audit-metadata-bodies`, then +again with `--apply`, to drop the stored content hashes from the audit and +activity copies. The 17.7 dry run no longer writes. A metadata version +token held from before the upgrade answers `409 METADATA_CONFLICT` once; +read the item again for a fresh one. + +After the first boot: + +- **Rotate every flow secret** — an inbound hook's `secret` and an `http` + node's `signingSecret` — once the boot log reports it moved into + `sys_flow_credential`, and hand the new value to whoever signs or + verifies. +- **Rotate the JWT signing keys, and revoke and re-mint the share links that + must stay private.** Their earlier values may have copies in the audit + trail, which the upgrade does not rewrite. +- **Resume, cancel or purge flow runs paused before the upgrade.** Their + stored variables still hold credentials in clear. +- **Optional, and only once you will not roll back past 17.7:** + `os secret rewrap`, then again with `--apply`, re-seals older secrets in + the new format. Nothing on the upgrade path runs it. +- **Drop the retired Localization keys** from any client that writes + `/api/settings/localization`: `date_format`, `time_format`, + `number_format`, `first_day_of_week`. A request naming one is refused + `400` whole. +- **External sign-in to an unverified local email address is refused** + (`account_not_linked`); it no longer links to that account silently. + +Full checklist: [17.7.0](https://objectstack.ai/docs/releases/v17/17-7#upgrade-checklist). diff --git a/content/docs/reference/cel.mdx b/content/docs/reference/cel.mdx index 282ceb7..033eda8 100644 --- a/content/docs/reference/cel.mdx +++ b/content/docs/reference/cel.mdx @@ -68,7 +68,8 @@ CEL expressions evaluate in a context with these top-level variables: | `record` | almost always | The current record being evaluated | | `previous` | on update hooks / change-detection | The pre-change state of the record (or `null`) | | `input` | actions, flow steps | The user-supplied input payload | -| `os.user` | always | `{ id, name, email, positions: string[], organizationId }` | +| `current_user` | when the evaluation has an acting user — **not** in a validation rule, which sees only `record` and `previous` | `{ id, name, email, positions: string[], organizationId }`, plus [`can()`](#permission-checks) | +| `os.user` | where `current_user` is | Another name for `current_user` — the same object | | `os.org` | always | Organisation / tenant context | | `os.env` | always | Environment variables exposed to expressions | @@ -106,6 +107,27 @@ CEL also includes the native `timestamp(...)`, `duration(...)`, Native CEL string helpers (`.contains(...)`, `.startsWith(...)`, `.matches(...)`, `.size()`) are always available. +### Permission checks + +Since ObjectStack 17.5, `current_user.can(object, verb)` asks whether the +acting user holds an object permission, answered from that user's +effective permissions: + +```ts +discount: Field.percent({ + label: 'Discount', + defaultValue: cel`current_user.can('crm_quote', 'edit') ? 10 : 0`, +}), +``` + +`object` is an object name and `verb` one of `read`, `create`, `edit` +(also `update`, `write`), `delete` (also `remove`), `export`, `transfer` +and `import`. The server answers it in a select option's `visibleWhen` +(an option the user may not pick is refused `invalid_option`), in formula +fields and in a CEL `defaultValue`. Where an evaluation carries no +permission data — a validation rule is one — `can()` is refused rather +than guessed, and the expression fails. + ## Examples **Formula field** — line-item total: diff --git a/content/docs/reference/cli.mdx b/content/docs/reference/cli.mdx index 47ce21e..a55b3e5 100644 --- a/content/docs/reference/cli.mdx +++ b/content/docs/reference/cli.mdx @@ -95,13 +95,17 @@ Templates: | `plugin` | Plugin scaffold for distributing reusable capabilities | | `empty` | Manifest + tsconfig only | -### `os create` — create a package, plugin, or example from template +### `os create` — scaffold a kernel code plugin ```bash os create plugin my-plugin -os create example my-example ``` +`os create plugin` writes a TypeScript package implementing the kernel's +`Plugin` contract, which `os init` does not. `os create example` was retired +in ObjectStack 17.4: it wrote a subset of what `os init` writes, and it now +exits with an error that names `os init`. + ## Build / validate commands | Command | Purpose | @@ -154,6 +158,65 @@ os meta register os meta delete ``` +## Database migrations — `os migrate` + +`os migrate` brings a deployment's database in line with its metadata, and +runs the one-off data migrations a release asks for. Every subcommand works +on the database `--database-url` names, which defaults to `$OS_DATABASE_URL` +and then to the project's own database — so name the deployment's database +explicitly. Each one boots the app to read its metadata. Back the database up +first. Which commands a given upgrade needs is on +[Upgrade and Rollback](/docs/operate/upgrade#version-specific-steps). + +### Schema + +| Command | What it does | +|---|---| +| `os migrate plan` | Shows how the database has drifted from the metadata, as safe, needs-confirm and destructive changes. Writes nothing | +| `os migrate apply` | Applies the non-destructive changes, after a prompt (`--yes` skips it). `--allow-destructive` also drops orphaned columns, tightens `NOT NULL` and narrows types | +| `os migrate multi-value-columns` | Moves a stale text column to JSON where a field declares `multiple: true`, the one type change `apply` never makes. A dry run until `--apply` | +| `os migrate unmapped-columns --object NAME` | Reads the values left in a retired field's columns, keyed by record id. Read-only. Since 17.7 no runtime door returns those columns, so this is how a conversion reads them before `apply --allow-destructive` drops them | +| `os migrate duplicates` | Reports identifiers already issued twice and the rows that block a unique index, as JSON. Read-only | + +```bash +os migrate plan # what would change; writes nothing +os migrate apply --allow-destructive --yes # also drop and tighten (CI / scripts) +os migrate unmapped-columns --object contact --json > contact-unmapped.json +``` + +### Data migrations + +Each of these rewrites rows rather than schema, and is a fact about your +database, not about the version you installed: the runtime does not run them +for you. Unless its row says otherwise, each is a dry run until you pass +`--apply`. + +| Command | What it does | +|---|---| +| `os migrate files-to-references` | (17.0) Converts file-field values stored inline to `sys_file` references. `--apply` records the flag that turns on strict media values for this deployment | +| `os migrate value-shapes` | (17.0) Scans reference and structured JSON values against their contract. `--apply` records its flag once a scan finds nothing | +| `os migrate meta --from 17` | Replays the metadata conversion chain over an app's **sources** and lists the edits it makes; `--out FILE` writes the converted stack. An authoring step: it does not touch the database | +| `os migrate meta --stored` | The same conversion over this deployment's **stored** metadata; `--apply` rewrites the rows | +| `os migrate account-issuer` | (17.5) Checks that the `sys_account.issuer` column can be dropped. Read-only; exits non-zero while two accounts would collide | +| `os migrate audit-metadata-bodies` | (17.6) Rewrites the copies of metadata bodies the audit trail and activity stream hold, removing credentials and stored content hashes | +| `os migrate summary-nulls` | Fills roll-up `count` / `sum` columns still stored as `NULL` on older parent rows | +| `os migrate recorded-by` | Rewrites the legacy `'system'` marker in metadata history to `NULL` | +| `os migrate resume` | Lists migration runs that were interrupted. Read-only until `--run ID`, which resumes or unwinds one | + +```bash +os migrate account-issuer # read-only pre-flight +os migrate audit-metadata-bodies # dry run: what it would rewrite +os migrate audit-metadata-bodies --apply # rewrite +``` + +### Secrets and stored files + +| Command | What it does | +|---|---| +| `os secret rewrap` | Re-seals older `sys_secret` ciphertext in the 17.7 format. A dry run until `--apply`; it must be told whether the host declares datasources in code (`--declared-datasources FILE` or `--no-declared-datasources`). ⚠️ A release before 17.7 cannot open what it writes | +| `os secret orphans` | Reports `sys_secret` rows nothing refers to any more. Deletes only with `--delete` | +| `os storage orphans` | Reports stored files nothing holds and no retention policy will ever reclaim. Writes nothing | + ## i18n commands ```bash @@ -226,4 +289,6 @@ os data query --help - **Developer building an app** → `os init` → `os dev` → `os compile` - **CI / release pipeline** → `os lint && os validate && os compile && os test` - **Production runtime** → `os serve` (typically inside the Docker image's `CMD`) +- **Upgrading a deployment** → the `os migrate` steps the + [upgrade page](/docs/operate/upgrade#version-specific-steps) lists for your versions - **Diagnostics** → `os doctor`, `os info`, `os explain` diff --git a/content/docs/reference/environment-variables.mdx b/content/docs/reference/environment-variables.mdx index c424731..f810bfb 100644 --- a/content/docs/reference/environment-variables.mdx +++ b/content/docs/reference/environment-variables.mdx @@ -61,8 +61,8 @@ supported combinations and the reasoning behind the refusal. ## Multi-organization deployments A **walled** tenancy posture puts up the per-organization isolation wall. It is -a licensed capability, and it makes two further decisions mandatory: the -runtime refuses to start until each is declared. Declaring either available +a licensed capability, and it makes three further decisions mandatory: the +runtime refuses to start until each is declared. Declaring any available value is accepted; not deciding is not. | Variable | Decides | @@ -70,9 +70,12 @@ value is accepted; not deciding is not. | `OS_TENANCY_POSTURE` | Whether organizations are isolated from one another. The walled values are `isolated` and `group`. A walled posture **requires a license**: without one the runtime exits at startup rather than serve traffic while pretending to be isolated. | | `OS_AUTH_MEMBERSHIP_POLICY` | What a fresh sign-up joins. `auto` binds every new user to the deployment's default organization — right for a single-organization box, wrong where a membership *is* the tenant boundary. `invite-only` grants membership only by an explicit act: creating a workspace, accepting an invitation, an admin, or SSO provisioning. Also settable in **Setup → Authentication → Membership**; setting it here pins it and makes the Setup field read-only. | | `OS_AI_STUDIO_AGENTS` | Which AI agents are mounted, as a comma-separated subset of `ask` and `build`. Unset mounts **both**. `ask` reads; `build` **authors metadata** — and metadata is scoped to the deployment, not to an organization, so on a shared database one customer's `build` turn rewrites the schema every other customer runs on. The AI seat does not cover this: it is one flag for both agents. A misspelled value fails the boot rather than quietly falling back to mounting both. | +| `OS_PLATFORM_OWNER_EMAIL` | Who the deployment's platform administrators are: one email address, or a comma-separated list. Under a walled posture it is the **only** source of platform-administrator standing — since ObjectStack 17.5 an `admin_full_access` grant no longer confers it there — and unset or blank, the runtime refuses to start. A listed account holds the standing only once its address is **verified**, so the deployment needs a way to verify it: an email transport, or a trusted federated sign-in. One unparseable entry voids the whole list. A change takes effect on restart. | -A single-organization deployment sees neither of the last two checks and may -leave both unset. +A single-organization deployment sees none of these checks. It may leave +the last three unset: the first account to register becomes the platform +administrator, unless `OS_PLATFORM_OWNER_EMAIL` names one, in which case +that verified account does. ## Multi-node @@ -113,11 +116,12 @@ the older URL — actually work. | `OS_ENVIRONMENT_ID` | The environment id a single-environment runtime reports as its own. A completed cloud binding persists this, after which a self-hosted runtime does not need it set. | | `OS_TRUSTED_ORIGINS` | Additional trusted origins, comma-separated. | | `OS_ROOT_DOMAIN` | Root domain used to trust subdomains in platform-SSO deployments. | -| `OS_DATABASE_DRIVER` | Overrides the driver otherwise inferred from the `OS_DATABASE_URL` scheme. Set it only when the scheme cannot be inferred; an unsupported value fails rather than falling back. | +| `OS_DATABASE_DRIVER` | Overrides the driver otherwise inferred from the `OS_DATABASE_URL` scheme. Set it only when the scheme cannot be inferred; an unsupported value fails rather than falling back. Since ObjectStack 17.7 the in-memory engine is refused as a boot store, whether it is named here (`memory`) or by a `memory://` URL. | | `OS_DATABASE_AUTH_TOKEN` | Auth token for managed drivers that use one. | | `OS_SKIP_SCHEMA_SYNC` | Set to `1` to skip the boot-time schema sync. Use when schema is managed out of band — the shipped stack already runs migrations as a separate one-shot step before any replica starts. | | `OS_TELEMETRY_DB` | A dedicated datasource for lifecycle-classed system data. Set to `0` to opt out, or to a path or URL to opt in. | | `OS_LIFECYCLE_DISABLED` | Set to `1` to disable the lifecycle service (retention reaper, table rotation, audit archiver). Retention windows themselves are tuned through the `lifecycle.retention_overrides` setting, not through environment variables. | +| `OS_AUTOMATION_SCHEDULED_WORK_ENABLED` | Whether the deployment runs scheduled work that apps and packages declare: time-triggered [flows](/docs/build/automation/flows#scheduled-run-on-a-clock) (a schedule, or a time-relative sweep) and packaged `defineJob` cron jobs. Only `true`, `1`, `on` or `yes` turn it on; **unset is off**, the ObjectStack runtime's default since 17.5, in every tenancy posture. While it is off, nothing on a clock arms, and each such flow is reported as disabled by deployment policy. The platform's own scheduled work (approval escalation, retention, message dispatch) is not gated. Under `isolated`, a scheduled flow or job must also declare the organization it runs as. ⚠️ Whether your ObjectOS image or the deploy-bundle template sets this for you is not something this page can promise: set it explicitly if your app depends on scheduled flows, and read the effective value with `os doctor`. | | `OS_MCP_SERVER_ENABLED` | Governs the Model Context Protocol server over HTTP at `/api/v1/mcp`, and only that surface. **On by default**: set an explicit falsy value (`false`, `0`, `off`, `no`) to disable it. An authenticated principal is always required. | | `OS_MCP_STDIO_ENABLED` | Set to a truthy value to auto-start the long-lived **stdio** MCP transport. Off by default, and independent of the HTTP surface above. | | `OS_MCP_STDIO_API_KEY` | The API key that stdio transport is principal-bound to, resolved through the same authorization chain as HTTP MCP, so record- and field-level security still apply. Fails closed: with stdio enabled and no resolvable key, the transport does not start. | @@ -136,6 +140,28 @@ nothing until you select one — **setting an endpoint alone does nothing.** | `OS_OBS_DEPLOYMENT_ENV` | `production` | The `deployment.environment` resource attribute. | | `OS_OTLP_FLUSH_MS` | — | Flush interval for the OTLP exporter, in milliseconds. | +## Data-contract escape hatches + +ObjectStack 17.0 made the platform refuse values that break their declared +contract, where it used to warn and accept them. These variables re-open the +old behaviour **temporarily**, for an integration you cannot fix today. Each +takes exactly `1`; anything else leaves the strict behaviour on. The +violation is still logged, so the drift stays visible. + +| Variable | Re-opens | +|---|---| +| `OS_ALLOW_LAX_ACTION_PARAMS` | An action call whose `params` miss a required parameter, break its `options` / `multiple` / `reference` declaration, or carry an undeclared key. Without it, such a call answers `400 VALIDATION_FAILED`. | +| `OS_ALLOW_LAX_MEDIA_VALUES` | A malformed `file` / `image` / `avatar` / `video` / `audio` value, where those values are checked strictly. | +| `OS_ALLOW_LAX_VALUE_SHAPES` | A malformed reference (`lookup`, `master_detail`, `user`, `tree`) or structured JSON value (`location`, `address`, …), where those values are checked strictly. | + +Media and value shapes are checked strictly on a database the 17.x runtime +created. A database upgraded from an earlier major is checked strictly only +once the matching migration has recorded its flag — see +[CLI → Data migrations](/docs/reference/cli#data-migrations). Do +not reach for `OS_DATA_VALUE_SHAPE_STRICT_ENABLED=1` to get there sooner: it +turns strict checking on for every value class at once, whatever this +deployment's data actually holds. + ## Pinning a system setting System settings — the tenant- and administrator-editable `ai`, `email`, @@ -159,6 +185,7 @@ they can be used. | `OS_ARTIFACT_FILE` | `OS_ARTIFACT_URL`. Nothing in the shipped runtime reads `OS_ARTIFACT_FILE`; a deployment that sets it gets the boot it would have got with nothing set, and no error naming the mistake. A local file is `file://` on the replacement, and the replacement carries the integrity pin. | | `OS_ARTIFACT_PATH` | `OS_ARTIFACT_URL`. **The ObjectOS runtime image refuses a boot that carries a non-default `OS_ARTIFACT_PATH`**, naming the replacement. Two spellings for "which artifact do I boot" is one dialect too many, and the retired one never carried an integrity pin. | | `OS_BUSINESS_DB_URL` | `OS_DATABASE_URL`, or a datasource declared in the artifact. Nothing reads `OS_BUSINESS_DB_URL`; a deployment that sets it and nothing else has no database configured at all. | +| `OS_LOCALIZATION_DATE_FORMAT`, `OS_LOCALIZATION_TIME_FORMAT`, `OS_LOCALIZATION_NUMBER_FORMAT`, `OS_LOCALIZATION_FIRST_DAY_OF_WEEK` | Nothing: ObjectStack 17.7 retired the settings they pinned, and dates, times, numbers and the week start now follow the language. See [Localization](/docs/configure/localization#dates-times-and-numbers-follow-the-language). | `OS_CLOUD_API_KEY` is **not** a variable a self-hosted deployment sets. It is the service credential the hosted cloud injects into runtimes it operates diff --git a/content/docs/reference/rest-api.de.mdx b/content/docs/reference/rest-api.de.mdx deleted file mode 100644 index f8b646d..0000000 --- a/content/docs/reference/rest-api.de.mdx +++ /dev/null @@ -1,230 +0,0 @@ ---- -title: REST API -description: Die HTTP-Schnittstelle, die ObjectOS bereitstellt — aus deinen Metadaten generiert, durch Berechtigungen eingegrenzt, per OpenAPI beschrieben. -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -Jedes Objekt, das du deklarierst, erhält automatisch einen vollständigen -Satz von REST-Endpunkten. Jede Action wird zu einem `POST`. Jeder Flow -wird zu einem `POST` auf `/flows/...`. Es gibt keine separate API-Schicht, -die du schreiben oder bereitstellen musst. - -Basis-URL: `https:///api/v1`. -Die OpenAPI-Spezifikation ist live unter [`/api/v1/openapi.json`](#discovery) verfügbar. - -## Authentication - -ObjectOS verwendet [Better Auth](https://www.better-auth.com/). Alle Routen -unter `/api/v1` erfordern eine authentifizierte Sitzung, sofern sie nicht -ausdrücklich als **public** markiert sind (siehe [Public forms](#public-forms)). -Die Authentifizierungsmethoden werden in `apps//auth.config.ts` -konfiguriert — typischerweise ein Session-Cookie oder ein Bearer-Token von -`/api/v1/auth/sign-in`. - -Berechtigungen werden **pro Route, pro Datensatz** geprüft. Routen, die unten -mit einem Berechtigungsschlüssel (z. B. `ai:chat`) annotiert sind, rufen -`requirePermission(...)` auf; der Zugriff auf Datensatzebene wird durch RBAC + -Row-Level Security + Field-Level Security innerhalb der Daten-Engine -durchgesetzt. - -## Discovery - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/api/v1` | GET | Service Discovery — listet alle registrierten Routen, die Version und den Scoping-Modus auf | -| `/api/v1/discovery` | GET | Expliziter Alias für das obige Discovery-Dokument | -| `/api/v1/openapi.json` | GET | OpenAPI-3.0-Spezifikation für jeden Endpunkt im laufenden Runtime | -| `/api/v1/search` | GET | Volltextsuche über durchsuchbare Felder aller Objekte | - -## API-Oberfläche einschränken - -Die automatisch generierten Endpunkte werden pro Objekt gesteuert, sodass du -ein Objekt von der API nehmen oder schreibgeschützt machen kannst, ohne -Routen-Code zu schreiben: - -- **`apiEnabled`** (boolean, **Standard `true`**) — auf `false` setzen, um das - Objekt vollständig von der REST-Oberfläche auszuschließen. Anfragen an seine - Routen liefern 404. -- **`apiMethods`** (optionale Whitelist) — wenn gesetzt, sind nur die - aufgeführten Operationen erreichbar; jede andere Operation wird auf der - REST-Ebene abgelehnt. Erlaubte Werte: `get`, `list`, `create`, `update`, - `delete`, `upsert`, `bulk`, `aggregate`, `history`, `search`, `restore`, - `purge`, `import`, `export`. - -```ts -// Ein schreibgeschütztes Referenzobjekt, niemals über die API beschreibbar: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// Ein internes Objekt, das die API niemals offenlegt: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -Beide werden jetzt auf der REST-Ebene durchgesetzt (ADR-0049) — frühere -Versionen parsten diese Eigenschaften, wendeten sie aber nicht an. - -## Data — `/api/v1/data/*` - -CRUD + erweiterte Abfragen für jedes Objekt, das du deklarierst. `:object` ist -der `name` des Objekts (snake_case). - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/data/:object` | GET | Auflisten / Abfragen — übergib `where`, `orderBy`, `limit`, `offset`, `cursor`, `expand`, `select` als Query-Parameter | -| `/data/:object/query` | POST | Erweiterte Abfrage — vollständiger ObjectQL-Body mit `groupBy` + `aggregations` (siehe [ObjectQL](./objectql)) | -| `/data/:object/:id` | GET | Einen Datensatz abrufen. Unterstützt `?select=` und `?expand=` | -| `/data/:object` | POST | Erstellen. Gibt `201` mit dem neuen Datensatz zurück | -| `/data/:object/:id` | PATCH | Aktualisieren. Übergib den Header `If-Match: ` (oder `expectedVersion` im Body) für optimistische Nebenläufigkeit — `409 CONCURRENT_UPDATE` bei einem Konflikt | -| `/data/:object/:id` | DELETE | Löschen. Gleiche `If-Match`-Regel | -| `/data/:object/import` | POST | Massenimport (CSV oder JSON). Body: `{ format, csv? \| rows?, mapping?, dryRun? }`. Maximal 5.000 Zeilen pro Anfrage | -| `/data/:object/export` | POST | Datensätze als `csv` / `xlsx` / `pdf` / `json` exportieren | -| `/data/:object/:id/shares` | GET / POST | Freigabe pro Datensatz — Freigaben auflisten, Zugriff gewähren | -| `/data/:object/:id/shares/:shareId` | DELETE | Eine Freigabe widerrufen | -| `/data/lead/:id/convert` | POST | Lead-Konvertierung im Salesforce-Stil (CRM-Vorlage). Body: `{ accountId?, contactId?, createOpportunity? }` | - -### Kurzes Beispiel - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI — `/api/v1/ai/*` - -Die Endpunkte, auf denen der [AI Builder](/docs/build/ai-builder) und deine -[Agenten](/docs/build/agents) aufsetzen. - -| Pfad | Methode | Berechtigung | Zweck | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | Verfügbare Modelle der konfigurierten Anbieter auflisten | -| `/ai/chat` | POST | `ai:chat` | Einmalige Chat-Completion (synchron) | -| `/ai/chat/stream` | POST | `ai:chat` | Streaming-Chat (SSE) | -| `/ai/complete` | POST | `ai:chat` | Roher Completion-Endpunkt | -| `/ai/conversations` | GET / POST | `ai:chat` | Persistente Konversationen auflisten / erstellen | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | Eine Konversation lesen / löschen | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | Eine Benutzernachricht anhängen und den Agenten ausführen | -| `/ai/pending-actions` | GET | `ai:read` | Die HITL-Freigabewarteschlange — von der KI vorgeschlagene Mutationen | -| `/ai/pending-actions/:id` | GET | `ai:read` | Eine eingereihte Mutation inspizieren | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | Die Mutation anwenden | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | Sie verwerfen | - -Die Trennung zwischen `ai:read` und `ai:approve` ist das -Sicherheitsprimitiv, das den AI Builder für Endbenutzer sicher macht. - -## Actions — `/api/v1/actions/*` - -Jede `*.action.ts`, die du deklarierst, wird zu einem Endpunkt. - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/actions/:action` | POST | Die Action aufrufen. Der Body ist das Input-Schema der Action. Berechtigungen und Eingabevalidierung stammen aus der Action-Deklaration | - -Jede Action wird der KI außerdem als `action_`-Tool bereitgestellt — siehe -[Actions](/docs/build/interface/actions). - -## Flows — `/api/v1/flows/*` - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/flows/:flow/start` | POST | Einen Flow starten. Der Body ist die Flow-Eingabe | -| `/flows/:flow/runs/:runId` | GET | Status + Schritt-Ausgabe eines laufenden / abgeschlossenen Flows | -| `/flows/:flow/runs/:runId/cancel` | POST | Einen laufenden Durchlauf abbrechen | - -## Metadata — `/api/v1/meta/*` - -Schreibgeschützte Introspektion der Metadaten des laufenden Runtime. Nützlich -für Tooling und die Console. - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/meta` | GET | Alle Metadatentypen (`object`, `view`, `action`, `flow`, `agent`, …) | -| `/meta/:type` | GET | Elemente eines Typs auflisten. Filtern mit `?package=` | -| `/meta/:type/:name` | GET | Ein Element abrufen. `?layers=true` liefert die 3-Zustands-Diff-Ansicht (base / package / overlay) | -| `/meta/:type/:name/references` | GET | Jedes Metadatenelement finden, das auf dieses verweist | -| `/meta/:type/:name/history` | GET | Audit-Verlauf für dieses Element | -| `/meta/:type/:section/:name` | GET / POST | Ein in Abschnitte gegliedertes Metadatenelement lesen / upserten | -| `/meta/:type/:section/:name` | DELETE | Ein in Abschnitte gegliedertes Element löschen | - -## Sharing & Freigaben - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | Freigaberegeln verwalten | -| `/sharing/rules/:idOrName/evaluate` | POST | Eine Regel testweise gegen einen Kontext ausführen | -| `/approvals/processes` | GET / POST | Definitionen von Genehmigungsprozessen | -| `/approvals/requests` | GET / POST | Genehmigungsanfragen einreichen / auflisten | -| `/approvals/requests/:id/approve` | POST | Entscheidung: genehmigen | -| `/approvals/requests/:id/reject` | POST | Entscheidung: ablehnen | - -## Reports & Suche - -| Pfad | Methode | Zweck | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | Report-CRUD | -| `/reports/:id/run` | POST | Einen Report ausführen — gibt aggregierte Zeilen zurück | -| `/reports/:id/schedule` | POST | Wiederkehrende Auslieferung planen | - -## Public forms - -Die einzigen Routen, die **unauthentifiziert** sind, per Opt-in über -`FormView.sharing.allowAnonymous`. - -| Pfad | Methode | Auth | -|:--|:--|:--| -| `/forms/:slug` | GET | public — gibt das Formularschema zurück | -| `/forms/:slug/submit` | POST | public — übermittelt eine Antwort | - -## Utilities - -| Pfad | Methode | Berechtigung | Zweck | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | Eine E-Mail über den konfigurierten Anbieter senden | - -## Fehler-Envelope - -Jede Antwort, die nicht 2xx ist, folgt derselben Struktur: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -Häufige Codes: `UNAUTHENTICATED`, `PERMISSION_DENIED`, -`VALIDATION_ERROR`, `NOT_FOUND`, `CONCURRENT_UPDATE`, -`RATE_LIMITED`, `INTERNAL`. - -## Versionierung - -`/api/v1` ist die **stabile** API. Breaking Changes gehen nach `/api/v2`, und -beide werden für ein Minor-Release parallel bereitgestellt. Die aktuelle -Version und der Deprecation-Horizont stehen in `GET /api/v1`. - -## Siehe auch - -- [ObjectQL](./objectql) — die von `/data/*` verwendete Abfragesprache -- [Field types](./field-types) — welche Formen Daten annehmen können -- [Build → Actions](/docs/build/interface/actions) — eigene Endpunkte deklarieren -- [Security](./security) — was jede Route prüft, bevor sie ausgeführt wird diff --git a/content/docs/reference/rest-api.es.mdx b/content/docs/reference/rest-api.es.mdx deleted file mode 100644 index 238d94e..0000000 --- a/content/docs/reference/rest-api.es.mdx +++ /dev/null @@ -1,226 +0,0 @@ ---- -title: REST API -description: La superficie HTTP que expone ObjectOS — generada a partir de tus metadatos, delimitada por permisos y descrita con OpenAPI. -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -Cada objeto que declaras obtiene automáticamente un conjunto completo de endpoints REST. -Cada acción se convierte en un `POST`. Cada flujo se convierte en un `POST` a -`/flows/...`. No hay que escribir ni desplegar una capa de API aparte. - -URL base: `https:///api/v1`. -La especificación OpenAPI está disponible en vivo en [`/api/v1/openapi.json`](#discovery). - -## Autenticación - -ObjectOS utiliza [Better Auth](https://www.better-auth.com/). Todas las rutas -bajo `/api/v1` requieren una sesión autenticada, salvo que estén marcadas -explícitamente como **públicas** (consulta [Formularios públicos](#public-forms)). Los métodos de autenticación -se configuran en `apps//auth.config.ts` — normalmente una cookie de sesión o -un token bearer obtenido de `/api/v1/auth/sign-in`. - -Los permisos se comprueban **por ruta y por registro**. Las rutas anotadas -a continuación con una clave de permiso (p. ej. `ai:chat`) invocan -`requirePermission(...)`; el acceso a nivel de registro lo aplica RBAC + -seguridad a nivel de fila + seguridad a nivel de campo dentro del motor de datos. - -## Discovery - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/api/v1` | GET | Descubrimiento de servicios — lista todas las rutas registradas, la versión y el modo de delimitación | -| `/api/v1/discovery` | GET | Alias explícito del documento de descubrimiento anterior | -| `/api/v1/openapi.json` | GET | Especificación OpenAPI 3.0 de cada endpoint del runtime en ejecución | -| `/api/v1/search` | GET | Búsqueda de texto completo en los campos indexables de todos los objetos | - -## Restringir la superficie de la API - -Los endpoints autogenerados se gobiernan por objeto, de modo que puedes retirar -un objeto de la API o hacerlo de solo lectura sin escribir código de rutas: - -- **`apiEnabled`** (boolean, **por defecto `true`**): establécelo en `false` - para omitir el objeto de la superficie REST por completo. Las solicitudes a - sus rutas devuelven 404. -- **`apiMethods`** (lista blanca opcional): cuando se establece, solo son - accesibles las operaciones listadas; cualquier otra operación se rechaza en la - capa REST. Valores permitidos: `get`, `list`, `create`, `update`, `delete`, - `upsert`, `bulk`, `aggregate`, `history`, `search`, `restore`, `purge`, - `import`, `export`. - -```ts -// Un objeto de referencia de solo lectura, nunca escribible por la API: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// Un objeto interno que la API nunca expone: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -Ambos se aplican ahora en la capa REST (ADR-0049): las versiones anteriores -analizaban estas propiedades pero no las aplicaban. - -## Datos — `/api/v1/data/*` - -CRUD + consultas avanzadas para cada objeto que declaras. `:object` es el -`name` del objeto (snake_case). - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/data/:object` | GET | Listar / consultar — pasa `where`, `orderBy`, `limit`, `offset`, `cursor`, `expand`, `select` como parámetros de consulta | -| `/data/:object/query` | POST | Consulta avanzada — cuerpo ObjectQL completo con `groupBy` + `aggregations` (consulta [ObjectQL](./objectql)) | -| `/data/:object/:id` | GET | Obtener un registro. Admite `?select=` y `?expand=` | -| `/data/:object` | POST | Crear. Devuelve `201` con el nuevo registro | -| `/data/:object/:id` | PATCH | Actualizar. Pasa la cabecera `If-Match: ` (o `expectedVersion` en el cuerpo) para concurrencia optimista — `409 CONCURRENT_UPDATE` en caso de conflicto | -| `/data/:object/:id` | DELETE | Eliminar. Misma regla de `If-Match` | -| `/data/:object/import` | POST | Importación masiva (CSV o JSON). Cuerpo: `{ format, csv? \| rows?, mapping?, dryRun? }`. Máximo 5.000 filas por solicitud | -| `/data/:object/export` | POST | Exportar registros como `csv` / `xlsx` / `pdf` / `json` | -| `/data/:object/:id/shares` | GET / POST | Compartición por registro — listar concesiones, conceder acceso | -| `/data/:object/:id/shares/:shareId` | DELETE | Revocar una compartición | -| `/data/lead/:id/convert` | POST | Conversión de lead al estilo Salesforce (plantilla CRM). Cuerpo: `{ accountId?, contactId?, createOpportunity? }` | - -### Ejemplo rápido - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI — `/api/v1/ai/*` - -Los endpoints sobre los que se apoyan el [AI Builder](/docs/build/ai-builder) y tus -[agentes](/docs/build/agents). - -| Ruta | Método | Permiso | Propósito | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | Listar los modelos disponibles de los proveedores configurados | -| `/ai/chat` | POST | `ai:chat` | Completado de chat de un solo paso (síncrono) | -| `/ai/chat/stream` | POST | `ai:chat` | Chat en streaming (SSE) | -| `/ai/complete` | POST | `ai:chat` | Endpoint de completado en bruto | -| `/ai/conversations` | GET / POST | `ai:chat` | Listar / crear conversaciones persistentes | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | Leer / eliminar una conversación | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | Añadir un mensaje del usuario y ejecutar el agente | -| `/ai/pending-actions` | GET | `ai:read` | La cola de aprobación HITL — mutaciones que la IA ha propuesto | -| `/ai/pending-actions/:id` | GET | `ai:read` | Inspeccionar una mutación en cola | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | Aplicar la mutación | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | Descartarla | - -La separación entre `ai:read` y `ai:approve` es la primitiva de seguridad -que hace que el AI Builder sea seguro para los usuarios finales. - -## Acciones — `/api/v1/actions/*` - -Cada `*.action.ts` que declaras se convierte en un endpoint. - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/actions/:action` | POST | Invocar la acción. El cuerpo es el esquema de entrada de la acción. Los permisos y la validación de entrada provienen de la declaración de la acción | - -Cada acción también se expone a la IA como una herramienta `action_` — consulta -[Acciones](/docs/build/interface/actions). - -## Flujos — `/api/v1/flows/*` - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/flows/:flow/start` | POST | Iniciar un flujo. El cuerpo es la entrada del flujo | -| `/flows/:flow/runs/:runId` | GET | Estado + salida de los pasos de un flujo en ejecución / completado | -| `/flows/:flow/runs/:runId/cancel` | POST | Cancelar una ejecución en curso | - -## Metadatos — `/api/v1/meta/*` - -Introspección de solo lectura de los metadatos del runtime en ejecución. Útil -para herramientas y para la Console. - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/meta` | GET | Todos los tipos de metadatos (`object`, `view`, `action`, `flow`, `agent`, …) | -| `/meta/:type` | GET | Listar los elementos de un tipo. Filtra con `?package=` | -| `/meta/:type/:name` | GET | Obtener un elemento. `?layers=true` devuelve la vista de diferencias de 3 estados (base / package / overlay) | -| `/meta/:type/:name/references` | GET | Encontrar todos los elementos de metadatos que hacen referencia a este | -| `/meta/:type/:name/history` | GET | Registro de auditoría de ese elemento | -| `/meta/:type/:section/:name` | GET / POST | Leer / insertar o actualizar un elemento de metadatos seccionado | -| `/meta/:type/:section/:name` | DELETE | Eliminar un elemento seccionado | - -## Compartición y aprobaciones - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | Gestionar reglas de compartición | -| `/sharing/rules/:idOrName/evaluate` | POST | Ejecutar en seco una regla contra un contexto | -| `/approvals/processes` | GET / POST | Definiciones de procesos de aprobación | -| `/approvals/requests` | GET / POST | Enviar / listar solicitudes de aprobación | -| `/approvals/requests/:id/approve` | POST | Decisión: aprobar | -| `/approvals/requests/:id/reject` | POST | Decisión: rechazar | - -## Informes y búsqueda - -| Ruta | Método | Propósito | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | CRUD de informes | -| `/reports/:id/run` | POST | Ejecutar un informe — devuelve filas agregadas | -| `/reports/:id/schedule` | POST | Programar entrega recurrente | - -## Formularios públicos - -Las únicas rutas que son **no autenticadas**, habilitadas de forma opcional mediante -`FormView.sharing.allowAnonymous`. - -| Ruta | Método | Autenticación | -|:--|:--|:--| -| `/forms/:slug` | GET | pública — devuelve el esquema del formulario | -| `/forms/:slug/submit` | POST | pública — envía una respuesta | - -## Utilidades - -| Ruta | Método | Permiso | Propósito | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | Enviar un correo electrónico a través del proveedor configurado | - -## Envoltorio de errores - -Toda respuesta distinta de 2xx sigue la misma forma: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -Códigos comunes: `UNAUTHENTICATED`, `PERMISSION_DENIED`, -`VALIDATION_ERROR`, `NOT_FOUND`, `CONCURRENT_UPDATE`, -`RATE_LIMITED`, `INTERNAL`. - -## Versionado - -`/api/v1` es la API **estable**. Los cambios incompatibles van a `/api/v2` y -ambas se sirven en paralelo durante una versión menor. La versión actual y -el horizonte de obsolescencia están en `GET /api/v1`. - -## Véase también - -- [ObjectQL](./objectql) — el lenguaje de consulta utilizado por `/data/*` -- [Tipos de campo](./field-types) — qué formas pueden adoptar los datos -- [Build → Acciones](/docs/build/interface/actions) — declarar endpoints personalizados -- [Seguridad](./security) — qué comprueba cada ruta antes de ejecutarse diff --git a/content/docs/reference/rest-api.fr.mdx b/content/docs/reference/rest-api.fr.mdx deleted file mode 100644 index 7229c35..0000000 --- a/content/docs/reference/rest-api.fr.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: API REST -description: La surface HTTP exposée par ObjectOS — générée à partir de vos métadonnées, cadrée par les permissions, décrite via OpenAPI. -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -Chaque objet que vous déclarez obtient automatiquement un ensemble complet de points de terminaison REST. -Chaque action devient un `POST`. Chaque flow devient un `POST` vers -`/flows/...`. Il n'y a aucune couche API distincte à écrire ou à déployer. - -URL de base : `https:///api/v1`. -La spécification OpenAPI est disponible en direct sur [`/api/v1/openapi.json`](#discovery). - -## Authentification - -ObjectOS utilise [Better Auth](https://www.better-auth.com/). Toutes les routes -sous `/api/v1` requièrent une session authentifiée, sauf si elles sont explicitement -marquées comme **publiques** (voir [Formulaires publics](#public-forms)). Les méthodes d'authentification sont -configurées dans `apps//auth.config.ts` — typiquement un cookie de session ou -un jeton bearer issu de `/api/v1/auth/sign-in`. - -Les permissions sont vérifiées **par route, par enregistrement**. Les routes annotées -ci-dessous avec une clé de permission (par ex. `ai:chat`) appellent -`requirePermission(...)` ; l'accès au niveau de l'enregistrement est appliqué par RBAC + -sécurité au niveau des lignes + sécurité au niveau des champs au sein du moteur de données. - -## Discovery - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/api/v1` | GET | Découverte de service — liste toutes les routes enregistrées, la version, le mode de cadrage | -| `/api/v1/discovery` | GET | Alias explicite du document de découverte ci-dessus | -| `/api/v1/openapi.json` | GET | Spécification OpenAPI 3.0 pour chaque point de terminaison du runtime en cours d'exécution | -| `/api/v1/search` | GET | Recherche plein texte sur les champs recherchables de tous les objets | - -## Restreindre la surface d'API - -Les endpoints générés automatiquement sont gouvernés par objet, de sorte que -vous pouvez retirer un objet de l'API ou le rendre en lecture seule sans écrire -aucun code de route : - -- **`apiEnabled`** (booléen, **par défaut `true`**) — définissez `false` pour - retirer entièrement l'objet de la surface REST. Les requêtes vers ses routes - renvoient 404. -- **`apiMethods`** (liste blanche optionnelle) — lorsqu'elle est définie, seules - les opérations listées sont accessibles ; toute autre opération est rejetée au - niveau de la couche REST. Valeurs autorisées : `get`, `list`, `create`, - `update`, `delete`, `upsert`, `bulk`, `aggregate`, `history`, `search`, - `restore`, `purge`, `import`, `export`. - -```ts -// Un objet de référence en lecture seule, jamais inscriptible via l'API : -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// Un objet interne que l'API n'expose jamais : -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -Les deux sont désormais appliqués au niveau de la couche REST (ADR-0049) — les -versions antérieures analysaient ces propriétés mais ne les appliquaient pas. - -## Données — `/api/v1/data/*` - -CRUD + requêtes avancées pour chaque objet que vous déclarez. `:object` est le -`name` de l'objet (snake_case). - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/data/:object` | GET | Lister / interroger — passez `where`, `orderBy`, `limit`, `offset`, `cursor`, `expand`, `select` comme paramètres de requête | -| `/data/:object/query` | POST | Requête avancée — corps ObjectQL complet avec `groupBy` + `aggregations` (voir [ObjectQL](./objectql)) | -| `/data/:object/:id` | GET | Récupérer un enregistrement. Prend en charge `?select=` et `?expand=` | -| `/data/:object` | POST | Créer. Renvoie `201` avec le nouvel enregistrement | -| `/data/:object/:id` | PATCH | Mettre à jour. Passez l'en-tête `If-Match: ` (ou `expectedVersion` dans le corps) pour la concurrence optimiste — `409 CONCURRENT_UPDATE` en cas de conflit | -| `/data/:object/:id` | DELETE | Supprimer. Même règle `If-Match` | -| `/data/:object/import` | POST | Import en masse (CSV ou JSON). Corps : `{ format, csv? \| rows?, mapping?, dryRun? }`. Maximum 5 000 lignes/requête | -| `/data/:object/export` | POST | Exporter les enregistrements en `csv` / `xlsx` / `pdf` / `json` | -| `/data/:object/:id/shares` | GET / POST | Partage par enregistrement — lister les octrois, accorder l'accès | -| `/data/:object/:id/shares/:shareId` | DELETE | Révoquer un partage | -| `/data/lead/:id/convert` | POST | Conversion de lead à la Salesforce (modèle CRM). Corps : `{ accountId?, contactId?, createOpportunity? }` | - -### Exemple rapide - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## IA — `/api/v1/ai/*` - -Les points de terminaison sur lesquels reposent l'[AI Builder](/docs/build/ai-builder) et vos -[agents](/docs/build/agents). - -| Chemin | Méthode | Permission | Objectif | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | Lister les modèles disponibles des fournisseurs configurés | -| `/ai/chat` | POST | `ai:chat` | Complétion de chat en un seul coup (synchrone) | -| `/ai/chat/stream` | POST | `ai:chat` | Chat en streaming (SSE) | -| `/ai/complete` | POST | `ai:chat` | Point de terminaison de complétion brute | -| `/ai/conversations` | GET / POST | `ai:chat` | Lister / créer des conversations persistantes | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | Lire / supprimer une conversation | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | Ajouter un message utilisateur et exécuter l'agent | -| `/ai/pending-actions` | GET | `ai:read` | La file d'approbation HITL — mutations proposées par l'IA | -| `/ai/pending-actions/:id` | GET | `ai:read` | Inspecter une mutation en file d'attente | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | Appliquer la mutation | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | La rejeter | - -La séparation entre `ai:read` et `ai:approve` est la primitive de sécurité -qui rend l'AI Builder sûr pour les utilisateurs finaux. - -## Actions — `/api/v1/actions/*` - -Chaque `*.action.ts` que vous déclarez devient un point de terminaison. - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/actions/:action` | POST | Invoquer l'action. Le corps correspond au schéma d'entrée de l'action. Les permissions et la validation des entrées proviennent de la déclaration de l'action | - -Chaque action est également exposée à l'IA sous forme d'outil `action_` — voir -[Actions](/docs/build/interface/actions). - -## Flows — `/api/v1/flows/*` - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/flows/:flow/start` | POST | Démarrer un flow. Le corps correspond à l'entrée du flow | -| `/flows/:flow/runs/:runId` | GET | Statut + sortie des étapes d'un flow en cours / terminé | -| `/flows/:flow/runs/:runId/cancel` | POST | Annuler une exécution en cours | - -## Métadonnées — `/api/v1/meta/*` - -Introspection en lecture seule des métadonnées du runtime en cours d'exécution. Utile -pour l'outillage et la Console. - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/meta` | GET | Tous les types de métadonnées (`object`, `view`, `action`, `flow`, `agent`, …) | -| `/meta/:type` | GET | Lister les éléments d'un type. Filtrer avec `?package=` | -| `/meta/:type/:name` | GET | Récupérer un élément. `?layers=true` renvoie la vue de diff à 3 états (base / package / overlay) | -| `/meta/:type/:name/references` | GET | Trouver chaque élément de métadonnées qui référence celui-ci | -| `/meta/:type/:name/history` | GET | Piste d'audit pour cet élément | -| `/meta/:type/:section/:name` | GET / POST | Lire / insérer-ou-mettre-à-jour un élément de métadonnées sectionné | -| `/meta/:type/:section/:name` | DELETE | Supprimer un élément sectionné | - -## Partage et approbations - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | Gérer les règles de partage | -| `/sharing/rules/:idOrName/evaluate` | POST | Exécuter une règle à blanc sur un contexte | -| `/approvals/processes` | GET / POST | Définitions de processus d'approbation | -| `/approvals/requests` | GET / POST | Soumettre / lister les demandes d'approbation | -| `/approvals/requests/:id/approve` | POST | Décision : approuver | -| `/approvals/requests/:id/reject` | POST | Décision : rejeter | - -## Rapports et recherche - -| Chemin | Méthode | Objectif | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | CRUD des rapports | -| `/reports/:id/run` | POST | Exécuter un rapport — renvoie des lignes agrégées | -| `/reports/:id/schedule` | POST | Planifier une diffusion récurrente | - -## Formulaires publics - -Les seules routes qui sont **non authentifiées**, activées de manière optionnelle via -`FormView.sharing.allowAnonymous`. - -| Chemin | Méthode | Authentification | -|:--|:--|:--| -| `/forms/:slug` | GET | publique — renvoie le schéma du formulaire | -| `/forms/:slug/submit` | POST | publique — soumet une réponse | - -## Utilitaires - -| Chemin | Méthode | Permission | Objectif | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | Envoyer un e-mail via le fournisseur configuré | - -## Enveloppe d'erreur - -Chaque réponse non-2xx suit la même forme : - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -Codes courants : `UNAUTHENTICATED`, `PERMISSION_DENIED`, -`VALIDATION_ERROR`, `NOT_FOUND`, `CONCURRENT_UPDATE`, -`RATE_LIMITED`, `INTERNAL`. - -## Versionnage - -`/api/v1` est l'API **stable**. Les changements incompatibles vont vers `/api/v2` et -les deux sont servies en parallèle pendant une version mineure. La version actuelle et -l'horizon de dépréciation se trouvent dans `GET /api/v1`. - -## Voir aussi - -- [ObjectQL](./objectql) — le langage de requête utilisé par `/data/*` -- [Types de champs](./field-types) — les formes que peuvent prendre les données -- [Build → Actions](/docs/build/interface/actions) — déclarer des points de terminaison personnalisés -- [Sécurité](./security) — ce que chaque route vérifie avant de s'exécuter diff --git a/content/docs/reference/rest-api.ja.mdx b/content/docs/reference/rest-api.ja.mdx deleted file mode 100644 index 0ee8a3c..0000000 --- a/content/docs/reference/rest-api.ja.mdx +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: REST API -description: ObjectOS が公開する HTTP インターフェース — メタデータから生成され、権限でスコープされ、OpenAPI で記述されます。 -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -宣言したすべてのオブジェクトに、完全な REST エンドポイントセットが自動的に付与されます。 -すべてのアクションは `POST` になります。すべてのフローは -`/flows/...` への `POST` になります。記述やデプロイが必要な独立した API レイヤーは存在しません。 - -ベース URL: `https:///api/v1`。 -OpenAPI 仕様は [`/api/v1/openapi.json`](#discovery) で公開されています。 - -## Authentication - -ObjectOS は [Better Auth](https://www.better-auth.com/) を使用します。 -`/api/v1` 配下のすべてのルートは、明示的に **public** とマークされていない限り、 -認証済みセッションを必要とします([Public forms](#public-forms) を参照)。認証方式は -`apps//auth.config.ts` で設定します。通常はセッション Cookie か、 -`/api/v1/auth/sign-in` から取得したベアラートークンです。 - -権限は **ルートごと、レコードごと** にチェックされます。以下で権限キー -(例: `ai:chat`)が付記されているルートは -`requirePermission(...)` を呼び出します。レコードレベルのアクセスは、データエンジン内部の -RBAC + 行レベルセキュリティ + フィールドレベルセキュリティによって強制されます。 - -## Discovery - -| Path | Method | 目的 | -|:--|:--|:--| -| `/api/v1` | GET | サービスディスカバリ — 登録されたすべてのルート、バージョン、スコープモードを一覧表示 | -| `/api/v1/discovery` | GET | 上記のディスカバリドキュメントに対する明示的なエイリアス | -| `/api/v1/openapi.json` | GET | 実行中のランタイムにあるすべてのエンドポイントの OpenAPI 3.0 仕様 | -| `/api/v1/search` | GET | すべてのオブジェクトの検索可能フィールドにまたがる全文検索 | - -## API 表面の制限 - -自動生成されるエンドポイントはオブジェクト単位で制御されるため、ルートコードを一切書かずに、 -オブジェクトを API から外したり読み取り専用にしたりできます: - -- **`apiEnabled`**(boolean、**デフォルト `true`**)— `false` に設定すると、オブジェクトを - REST 表面から完全に除外します。そのルートへのリクエストは 404 になります。 -- **`apiMethods`**(任意のホワイトリスト)— 設定すると、リストされた操作のみが到達可能になり、 - それ以外の操作は REST レイヤーで拒否されます。許可される値: `get`、`list`、`create`、 - `update`、`delete`、`upsert`、`bulk`、`aggregate`、`history`、`search`、`restore`、 - `purge`、`import`、`export`。 - -```ts -// 読み取り専用の参照オブジェクト。API 経由では書き込み不可: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// API が決して公開しない内部オブジェクト: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -どちらも現在は REST レイヤーで強制適用されます(ADR-0049)— 以前のバージョンではこれらの -プロパティをパースするだけで、適用していませんでした。 - -## Data — `/api/v1/data/*` - -宣言したすべてのオブジェクトに対する CRUD + 高度なクエリ。`:object` は -オブジェクトの `name`(snake_case)です。 - -| Path | Method | 目的 | -|:--|:--|:--| -| `/data/:object` | GET | リスト / クエリ — クエリパラメータとして `where`、`orderBy`、`limit`、`offset`、`cursor`、`expand`、`select` を渡します | -| `/data/:object/query` | POST | 高度なクエリ — `groupBy` + `aggregations` を含む完全な ObjectQL ボディ([ObjectQL](./objectql) を参照) | -| `/data/:object/:id` | GET | 1 件のレコードを取得。`?select=` と `?expand=` をサポート | -| `/data/:object` | POST | 作成。新しいレコードとともに `201` を返します | -| `/data/:object/:id` | PATCH | 更新。楽観的並行性制御のために `If-Match: ` ヘッダー(またはボディ内の `expectedVersion`)を渡します — 競合時は `409 CONCURRENT_UPDATE` | -| `/data/:object/:id` | DELETE | 削除。同じ `If-Match` ルールが適用されます | -| `/data/:object/import` | POST | 一括インポート(CSV または JSON)。ボディ: `{ format, csv? \| rows?, mapping?, dryRun? }`。リクエストあたり最大 5,000 行 | -| `/data/:object/export` | POST | レコードを `csv` / `xlsx` / `pdf` / `json` としてエクスポート | -| `/data/:object/:id/shares` | GET / POST | レコードごとの共有 — 付与の一覧表示、アクセスの付与 | -| `/data/:object/:id/shares/:shareId` | DELETE | 共有を取り消し | -| `/data/lead/:id/convert` | POST | Salesforce 風のリード変換(CRM テンプレート)。ボディ: `{ accountId?, contactId?, createOpportunity? }` | - -### Quick example - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI — `/api/v1/ai/*` - -[AI Builder](/docs/build/ai-builder) と -[エージェント](/docs/build/agents) が動作するためのエンドポイントです。 - -| Path | Method | Permission | 目的 | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | 設定されたプロバイダーから利用可能なモデルを一覧表示 | -| `/ai/chat` | POST | `ai:chat` | 単発のチャット補完(同期) | -| `/ai/chat/stream` | POST | `ai:chat` | ストリーミングチャット(SSE) | -| `/ai/complete` | POST | `ai:chat` | 生の補完エンドポイント | -| `/ai/conversations` | GET / POST | `ai:chat` | 永続的な会話の一覧表示 / 作成 | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | 会話の読み取り / 削除 | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | ユーザーメッセージを追加してエージェントを実行 | -| `/ai/pending-actions` | GET | `ai:read` | HITL 承認キュー — AI が提案したミューテーション | -| `/ai/pending-actions/:id` | GET | `ai:read` | キューに入ったミューテーションを確認 | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | ミューテーションを適用 | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | 破棄 | - -`ai:read` と `ai:approve` の分離は、AI Builder をエンドユーザーにとって安全なものにする -セキュリティプリミティブです。 - -## Actions — `/api/v1/actions/*` - -宣言したすべての `*.action.ts` がエンドポイントになります。 - -| Path | Method | 目的 | -|:--|:--|:--| -| `/actions/:action` | POST | アクションを呼び出します。ボディはアクションの入力スキーマです。権限と入力検証はアクション宣言から得られます | - -各アクションは `action_` ツールとして AI にも公開されます — -[Actions](/docs/build/interface/actions) を参照してください。 - -## Flows — `/api/v1/flows/*` - -| Path | Method | 目的 | -|:--|:--|:--| -| `/flows/:flow/start` | POST | フローを開始します。ボディはフローの入力です | -| `/flows/:flow/runs/:runId` | GET | 実行中 / 完了したフローのステータス + ステップ出力 | -| `/flows/:flow/runs/:runId/cancel` | POST | 進行中の実行をキャンセル | - -## Metadata — `/api/v1/meta/*` - -実行中のランタイムのメタデータへの読み取り専用のイントロスペクション。 -ツールや Console に役立ちます。 - -| Path | Method | 目的 | -|:--|:--|:--| -| `/meta` | GET | すべてのメタデータタイプ(`object`、`view`、`action`、`flow`、`agent`、…) | -| `/meta/:type` | GET | あるタイプの項目を一覧表示。`?package=` でフィルタ | -| `/meta/:type/:name` | GET | 1 件の項目を取得。`?layers=true` で 3 状態の差分ビュー(base / package / overlay)を返します | -| `/meta/:type/:name/references` | GET | この項目を参照しているすべてのメタデータ項目を検索 | -| `/meta/:type/:name/history` | GET | その項目の監査証跡 | -| `/meta/:type/:section/:name` | GET / POST | セクション化されたメタデータ項目の読み取り / アップサート | -| `/meta/:type/:section/:name` | DELETE | セクション化された項目を削除 | - -## Sharing & approvals - -| Path | Method | 目的 | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | 共有ルールを管理 | -| `/sharing/rules/:idOrName/evaluate` | POST | コンテキストに対してルールをドライラン | -| `/approvals/processes` | GET / POST | 承認プロセスの定義 | -| `/approvals/requests` | GET / POST | 承認リクエストの送信 / 一覧表示 | -| `/approvals/requests/:id/approve` | POST | 決定: 承認 | -| `/approvals/requests/:id/reject` | POST | 決定: 却下 | - -## Reports & search - -| Path | Method | 目的 | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | レポートの CRUD | -| `/reports/:id/run` | POST | レポートを実行 — 集計された行を返します | -| `/reports/:id/schedule` | POST | 定期配信をスケジュール | - -## Public forms - -`FormView.sharing.allowAnonymous` でオプトインする、唯一の **認証なし** のルートです。 - -| Path | Method | Auth | -|:--|:--|:--| -| `/forms/:slug` | GET | public — フォームスキーマを返します | -| `/forms/:slug/submit` | POST | public — 応答を送信します | - -## Utilities - -| Path | Method | Permission | 目的 | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | 設定されたプロバイダー経由でメールを送信 | - -## Error envelope - -すべての非 2xx 応答は同じ形式に従います: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -一般的なコード: `UNAUTHENTICATED`、`PERMISSION_DENIED`、 -`VALIDATION_ERROR`、`NOT_FOUND`、`CONCURRENT_UPDATE`、 -`RATE_LIMITED`、`INTERNAL`。 - -## Versioning - -`/api/v1` は **安定版** API です。破壊的変更は `/api/v2` に入り、 -両方が 1 つのマイナーリリースの間並行して提供されます。現在のバージョンと -非推奨化の予定は `GET /api/v1` にあります。 - -## See also - -- [ObjectQL](./objectql) — `/data/*` で使用されるクエリ言語 -- [Field types](./field-types) — データが取り得る形状 -- [Build → Actions](/docs/build/interface/actions) — カスタムエンドポイントを宣言 -- [Security](./security) — すべてのルートが実行前にチェックする内容 diff --git a/content/docs/reference/rest-api.ko.mdx b/content/docs/reference/rest-api.ko.mdx deleted file mode 100644 index 297d63d..0000000 --- a/content/docs/reference/rest-api.ko.mdx +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: REST API -description: ObjectOS가 노출하는 HTTP 표면 — 메타데이터에서 생성되고, 권한으로 범위가 지정되며, OpenAPI로 기술됩니다. -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -선언하는 모든 객체는 자동으로 완전한 REST 엔드포인트 세트를 갖게 됩니다. -모든 액션은 `POST`가 되고, 모든 플로우는 -`/flows/...`로의 `POST`가 됩니다. 별도로 작성하거나 배포할 API 계층이 없습니다. - -기본 URL: `https:///api/v1`. -OpenAPI 사양은 [`/api/v1/openapi.json`](#discovery)에서 실시간으로 제공됩니다. - -## Authentication - -ObjectOS는 [Better Auth](https://www.better-auth.com/)를 사용합니다. `/api/v1` -아래의 모든 라우트는 명시적으로 **public**으로 표시되지 않는 한 인증된 세션이 -필요합니다([Public forms](#public-forms) 참고). 인증 방식은 -`apps//auth.config.ts`에서 구성되며, 일반적으로 세션 쿠키 또는 -`/api/v1/auth/sign-in`에서 받은 bearer 토큰입니다. - -권한은 **라우트별, 레코드별**로 확인됩니다. 아래에서 권한 키(예: `ai:chat`)가 -표시된 라우트는 `requirePermission(...)`을 호출하며, 레코드 수준 접근은 데이터 -엔진 내부의 RBAC + 행 수준 보안 + 필드 수준 보안에 의해 적용됩니다. - -## Discovery - -| Path | Method | Purpose | -|:--|:--|:--| -| `/api/v1` | GET | 서비스 디스커버리 — 등록된 모든 라우트, 버전, 범위 지정 모드를 나열 | -| `/api/v1/discovery` | GET | 위 디스커버리 문서에 대한 명시적 별칭 | -| `/api/v1/openapi.json` | GET | 실행 중인 런타임의 모든 엔드포인트에 대한 OpenAPI 3.0 사양 | -| `/api/v1/search` | GET | 모든 객체의 검색 가능 필드에 대한 전문(full-text) 검색 | - -## Restricting the API surface - -자동 생성된 엔드포인트는 객체 단위로 제어되므로, 라우트 코드를 작성하지 않고도 -객체를 API에서 제외하거나 읽기 전용으로 만들 수 있습니다: - -- **`apiEnabled`** (boolean, **기본값 `true`**) — `false`로 설정하면 객체가 REST - 표면에서 완전히 제외됩니다. 해당 라우트로의 요청은 404를 반환합니다. -- **`apiMethods`** (선택적 화이트리스트) — 설정하면 나열된 작업만 도달 가능하며, - 그 외 작업은 REST 계층에서 거부됩니다. 허용 값: `get`, `list`, `create`, - `update`, `delete`, `upsert`, `bulk`, `aggregate`, `history`, `search`, - `restore`, `purge`, `import`, `export`. - -```ts -// API로는 절대 쓸 수 없는 읽기 전용 참조 객체: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// API가 절대 노출하지 않는 내부 객체: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -이 둘은 이제 REST 계층에서 강제됩니다(ADR-0049) — 이전 버전은 이 속성들을 -파싱했지만 적용하지는 않았습니다. - -## Data — `/api/v1/data/*` - -선언하는 모든 객체에 대한 CRUD + 고급 쿼리. `:object`는 객체의 -`name`입니다(snake_case). - -| Path | Method | Purpose | -|:--|:--|:--| -| `/data/:object` | GET | 목록 / 쿼리 — `where`, `orderBy`, `limit`, `offset`, `cursor`, `expand`, `select`를 쿼리 파라미터로 전달 | -| `/data/:object/query` | POST | 고급 쿼리 — `groupBy` + `aggregations`를 포함한 완전한 ObjectQL 본문([ObjectQL](./objectql) 참고) | -| `/data/:object/:id` | GET | 레코드 하나 조회. `?select=` 및 `?expand=` 지원 | -| `/data/:object` | POST | 생성. 새 레코드와 함께 `201` 반환 | -| `/data/:object/:id` | PATCH | 업데이트. 낙관적 동시성 제어를 위해 `If-Match: ` 헤더(또는 본문의 `expectedVersion`)를 전달 — 충돌 시 `409 CONCURRENT_UPDATE` | -| `/data/:object/:id` | DELETE | 삭제. 동일한 `If-Match` 규칙 적용 | -| `/data/:object/import` | POST | 대량 가져오기(CSV 또는 JSON). 본문: `{ format, csv? \| rows?, mapping?, dryRun? }`. 요청당 최대 5,000행 | -| `/data/:object/export` | POST | 레코드를 `csv` / `xlsx` / `pdf` / `json`으로 내보내기 | -| `/data/:object/:id/shares` | GET / POST | 레코드별 공유 — 권한 부여 목록 조회, 접근 권한 부여 | -| `/data/:object/:id/shares/:shareId` | DELETE | 공유 취소 | -| `/data/lead/:id/convert` | POST | Salesforce 스타일의 리드 전환(CRM 템플릿). 본문: `{ accountId?, contactId?, createOpportunity? }` | - -### Quick example - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI — `/api/v1/ai/*` - -[AI Builder](/docs/build/ai-builder)와 [agents](/docs/build/agents)가 사용하는 -엔드포인트입니다. - -| Path | Method | Permission | Purpose | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | 구성된 제공자에서 사용 가능한 모델 나열 | -| `/ai/chat` | POST | `ai:chat` | 단발성 채팅 완성(동기) | -| `/ai/chat/stream` | POST | `ai:chat` | 스트리밍 채팅(SSE) | -| `/ai/complete` | POST | `ai:chat` | 원시(raw) 완성 엔드포인트 | -| `/ai/conversations` | GET / POST | `ai:chat` | 영속 대화 목록 조회 / 생성 | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | 대화 읽기 / 삭제 | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | 사용자 메시지를 추가하고 에이전트 실행 | -| `/ai/pending-actions` | GET | `ai:read` | HITL 승인 큐 — AI가 제안한 변경 사항 | -| `/ai/pending-actions/:id` | GET | `ai:read` | 큐에 대기 중인 변경 사항 검사 | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | 변경 사항 적용 | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | 변경 사항 폐기 | - -`ai:read`와 `ai:approve`의 분리는 AI Builder를 최종 사용자에게 안전하게 -만드는 보안 기본 요소입니다. - -## Actions — `/api/v1/actions/*` - -선언하는 모든 `*.action.ts`는 엔드포인트가 됩니다. - -| Path | Method | Purpose | -|:--|:--|:--| -| `/actions/:action` | POST | 액션 호출. 본문은 액션의 입력 스키마. 권한과 입력 검증은 액션 선언에서 가져옴 | - -각 액션은 AI에게도 `action_` 도구로 노출됩니다 — -[Actions](/docs/build/interface/actions) 참고. - -## Flows — `/api/v1/flows/*` - -| Path | Method | Purpose | -|:--|:--|:--| -| `/flows/:flow/start` | POST | 플로우 시작. 본문은 플로우 입력 | -| `/flows/:flow/runs/:runId` | GET | 실행 중 / 완료된 플로우의 상태 + 단계 출력 | -| `/flows/:flow/runs/:runId/cancel` | POST | 진행 중인 실행 취소 | - -## Metadata — `/api/v1/meta/*` - -실행 중인 런타임의 메타데이터에 대한 읽기 전용 인트로스펙션. 도구와 -Console에 유용합니다. - -| Path | Method | Purpose | -|:--|:--|:--| -| `/meta` | GET | 모든 메타데이터 유형(`object`, `view`, `action`, `flow`, `agent`, …) | -| `/meta/:type` | GET | 한 유형의 항목 나열. `?package=`로 필터링 | -| `/meta/:type/:name` | GET | 항목 하나 조회. `?layers=true`는 3-state 디프 뷰(base / package / overlay) 반환 | -| `/meta/:type/:name/references` | GET | 이 항목을 참조하는 모든 메타데이터 항목 찾기 | -| `/meta/:type/:name/history` | GET | 해당 항목의 감사 추적 | -| `/meta/:type/:section/:name` | GET / POST | 섹션화된 메타데이터 항목 읽기 / upsert | -| `/meta/:type/:section/:name` | DELETE | 섹션화된 항목 삭제 | - -## Sharing & approvals - -| Path | Method | Purpose | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | 공유 규칙 관리 | -| `/sharing/rules/:idOrName/evaluate` | POST | 컨텍스트에 대해 규칙을 모의 실행(dry-run) | -| `/approvals/processes` | GET / POST | 승인 프로세스 정의 | -| `/approvals/requests` | GET / POST | 승인 요청 제출 / 나열 | -| `/approvals/requests/:id/approve` | POST | 결정: 승인 | -| `/approvals/requests/:id/reject` | POST | 결정: 거부 | - -## Reports & search - -| Path | Method | Purpose | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | 리포트 CRUD | -| `/reports/:id/run` | POST | 리포트 실행 — 집계된 행 반환 | -| `/reports/:id/schedule` | POST | 반복 전달 예약 | - -## Public forms - -**인증되지 않은** 유일한 라우트로, `FormView.sharing.allowAnonymous`를 통해 -옵트인합니다. - -| Path | Method | Auth | -|:--|:--|:--| -| `/forms/:slug` | GET | public — 폼 스키마 반환 | -| `/forms/:slug/submit` | POST | public — 응답 제출 | - -## Utilities - -| Path | Method | Permission | Purpose | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | 구성된 제공자를 통해 이메일 발송 | - -## Error envelope - -2xx가 아닌 모든 응답은 동일한 형태를 따릅니다: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -일반적인 코드: `UNAUTHENTICATED`, `PERMISSION_DENIED`, -`VALIDATION_ERROR`, `NOT_FOUND`, `CONCURRENT_UPDATE`, -`RATE_LIMITED`, `INTERNAL`. - -## Versioning - -`/api/v1`은 **안정(stable)** API입니다. 호환성을 깨는 변경 사항은 `/api/v2`로 -가며, 두 버전은 하나의 마이너 릴리스 동안 병렬로 제공됩니다. 현재 버전과 -지원 중단(deprecation) 기한은 `GET /api/v1`에 있습니다. - -## See also - -- [ObjectQL](./objectql) — `/data/*`가 사용하는 쿼리 언어 -- [Field types](./field-types) — 데이터가 가질 수 있는 형태 -- [Build → Actions](/docs/build/interface/actions) — 커스텀 엔드포인트 선언 -- [Security](./security) — 모든 라우트가 실행 전에 확인하는 것 diff --git a/content/docs/reference/rest-api.mdx b/content/docs/reference/rest-api.mdx index 9b32e57..b29c6f6 100644 --- a/content/docs/reference/rest-api.mdx +++ b/content/docs/reference/rest-api.mdx @@ -5,8 +5,9 @@ description: The HTTP surface ObjectOS exposes — generated from your metadata, --- Every object you declare gets a full REST endpoint set automatically. -Every action becomes a `POST`. Every flow becomes a `POST` to -`/flows/...`. There is no separate API layer to write or deploy. +Every action becomes a `POST`. Every flow can be started with a `POST` to +`/automation/:name/trigger`. There is no separate API layer to write or +deploy. Base URL: `https:///api/v1`. OpenAPI spec is live at [`/api/v1/openapi.json`](#discovery). @@ -82,12 +83,12 @@ object's `name` (snake_case). | Path | Method | Purpose | |:--|:--|:--| -| `/data/:object` | GET | List / query — pass `where`, `orderBy`, `limit`, `offset`, `cursor`, `expand`, `select` as query params | +| `/data/:object` | GET | List / query — pass `where`, `orderBy`, `limit`, `offset`, `expand`, `select` as query params | | `/data/:object/query` | POST | Advanced query — full ObjectQL body with `groupBy` + `aggregations` (see [ObjectQL](./objectql)) | | `/data/:object/:id` | GET | Fetch one record. Supports `?select=` and `?expand=` | | `/data/:object` | POST | Create. Returns `201` with the new record | -| `/data/:object/:id` | PATCH | Update. Pass `If-Match: ` header (or `expectedVersion` in body) for optimistic concurrency — `409 CONCURRENT_UPDATE` on conflict | -| `/data/:object/:id` | DELETE | Delete. Same `If-Match` rule | +| `/data/:object/:id` | PATCH | Update. Pass `If-Match: ` header (or `expectedVersion` in body) for optimistic concurrency — `409 CONCURRENT_UPDATE` on conflict. A record you cannot read answers `404 RECORD_NOT_FOUND`, the same as one that does not exist | +| `/data/:object/:id` | DELETE | Delete. Same `If-Match` and `404` rules | | `/data/:object/import` | POST | Bulk import (CSV or JSON). Body: `{ format, csv? \| rows?, mapping?, dryRun? }`. Max 5,000 rows/request | | `/data/:object/export` | POST | Export records as `csv` / `xlsx` / `pdf` / `json` | | `/data/:object/:id/shares` | GET / POST | Per-record sharing — list grants, grant access | @@ -103,12 +104,19 @@ Authorization: Bearer ```json { - "items": [ { "id": "...", "subject": "...", "status": "open" } ], + "object": "support_ticket", + "records": [ { "id": "...", "subject": "...", "status": "open" } ], "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." + "hasMore": true } ``` +Page with `limit` and `offset`, or, for a stable walk through a large set, +with a `where` on your sort key (`{"created_at": {"$lt": ""}}`) +beside the same `orderBy`. There is no cursor paging: ObjectStack 17.0 +removed `cursor`, which no driver had ever implemented, so every page came +back identical. + ## AI — `/api/v1/ai/*` The endpoints the [AI Builder](/docs/build/ai-builder) and your @@ -137,33 +145,64 @@ Every `*.action.ts` you declare becomes an endpoint. | Path | Method | Purpose | |:--|:--|:--| -| `/actions/:action` | POST | Invoke the action. Body is the action's input schema. Permissions and input validation come from the action declaration | +| `/actions/:object/:action` | POST | Invoke an action declared on an object. Body is the action's input schema. Permissions and input validation come from the action declaration | +| `/actions/:object/:action/:recordId` | POST | The same, on one record | +| `/actions/:action` | POST | Invoke a global action (one bound to no object) | -Each action is also exposed to the AI as a `action_` tool — see -[Actions](/docs/build/interface/actions). +An action is callable by AI agents only when its author opts in: +`ai: { exposed: true }` with an `ai.description` of at least 40 characters, +on an action that runs without a UI (`script`, `flow` or `api`). See +[Actions](/docs/build/interface/actions) and [MCP](/docs/configure/mcp). +An action of type `flow` answers +with the [flow status table](#flow-status-table) below. -## Flows — `/api/v1/flows/*` +## Automation — `/api/v1/automation/*` | Path | Method | Purpose | |:--|:--|:--| -| `/flows/:flow/start` | POST | Start a flow. Body is the flow input | -| `/flows/:flow/runs/:runId` | GET | Status + step output of a running / completed flow | -| `/flows/:flow/runs/:runId/cancel` | POST | Cancel an in-flight run | +| `/automation/:name/trigger` | POST | Start a flow. Inputs go in `params`, under the names of the flow's `isInput` variables | +| `/automation/:name/runs` | GET | List the flow's runs | +| `/automation/:name/runs/:runId` | GET | Status and step output of one run | +| `/automation/:name/runs/:runId/resume` | POST | Resume a paused run. The body takes only `inputs`, `variables`, `output` and `branchLabel` | +| `/automation/:name/runs/:runId/cancel` | POST | Cancel an in-flight run. Needs platform-administrator standing; anyone else is refused `403` | +| `/automation/:name/toggle` | POST | Switch a **packaged** flow on or off. Requires `manage_metadata`; a flow authored in the deployment is refused `409` — switch it with its own `status` instead | +| `/automation/_status` | GET | Which flows are enabled and bound | + +### Flow status table + +The trigger route, an action of type `flow` and a declared `type: 'flow'` +endpoint all answer the same statuses. Branch on the HTTP status, not on a +`success` flag inside the body: + +| Status | Code | Means | +|:--|:--|:--| +| `200` | — | The run dispatched. Its `status` is `completed`, `paused` (it waits on a screen, an approval or a timer), or `refused` (it reached an `end` node with `outcome: 'refused'`; the text is in `refusalMessage`) | +| `404` | `RESOURCE_NOT_FOUND` | No flow has that name | +| `409` | `FLOW_DISABLED` | The flow is switched off | +| `422` | `FLOW_NO_START_NODE` | The stored definition has no start node and cannot run | +| `400` | `FLOW_FAILED` | The run dispatched and failed; the details carry the flow's `errorMessage` | ## Metadata — `/api/v1/meta/*` -Read-only introspection into the running runtime's metadata. Useful -for tooling and the UI. +Introspection into the running runtime's metadata, and the door Studio +saves through. Useful for tooling and the UI. | Path | Method | Purpose | |:--|:--|:--| | `/meta` | GET | All metadata types (`object`, `view`, `action`, `flow`, `agent`, …) | | `/meta/:type` | GET | List items of a type. Filter with `?package=` | -| `/meta/:type/:name` | GET | Fetch one item. `?layers=true` returns the 3-state diff view (base / package / overlay) | +| `/meta/:type/:name` | GET | Fetch one item | +| `/meta/:type/:name` | PUT / DELETE | Save or delete one item. Needs an authoring capability | +| `/meta/:type/:name/layers` | GET | The item as three layers: code, overlay and effective. Replaces the deprecated `?layers=true` | | `/meta/:type/:name/references` | GET | Find every metadata item that references this one | -| `/meta/:type/:name/history` | GET | Audit trail for that item | -| `/meta/:type/:section/:name` | GET / POST | Read / upsert a sectioned metadata item | -| `/meta/:type/:section/:name` | DELETE | Delete a sectioned item | +| `/meta/:type/:name/history` | GET | Change history for that item. Needs an authoring capability | + +`/meta/:type/:name` is the only address of an item. Percent-encode a name +that carries a slash: `GET /api/v1/meta/lead/views%2Fall_leads`. The +sectioned `/meta/:type/:section/:name` routes were retired in ObjectStack +17.3 and answer `404 ROUTE_NOT_FOUND`. Reading `datasource` or +`external_catalog` metadata, and saving `datasource` metadata, needs +`manage_platform_settings`. ## Sharing & approvals @@ -176,18 +215,23 @@ for tooling and the UI. | `/approvals/requests/:id/approve` | POST | Decision: approve | | `/approvals/requests/:id/reject` | POST | Decision: reject | -## Reports & search +## Reports -| Path | Method | Purpose | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | Report CRUD | -| `/reports/:id/run` | POST | Execute a report — returns aggregated rows | -| `/reports/:id/schedule` | POST | Schedule recurring delivery | +There is no saved-report API. ObjectStack 17.5 removed `/reports`, +`/reports/:id/run` and `/reports/:id/schedule`, which now answer `404`, +and scheduled report delivery went with them, with no replacement. A +report is metadata, read through `/meta/report/:name`, and its numbers +come from analytics (`POST /analytics/query`). Analytics answers +`403 PERMISSION_DENIED` to a caller who cannot read the object, the same +as `/data` does. ## Public forms -The only routes that are **unauthenticated**, opt-in via -`FormView.sharing.allowAnonymous`. +The only routes that are **unauthenticated**. A form view opts in through +its `sharing`, and all three keys are required: `enabled: true`, +`allowAnonymous: true`, and a `publicLink` slug. Since ObjectStack 17.7 a +form that sets only the last two answers `404 FORM_NOT_FOUND` — see +[Views → Public forms](/docs/build/interface/views#public-forms). | Path | Method | Auth | |:--|:--|:--| @@ -202,21 +246,35 @@ The only routes that are **unauthenticated**, opt-in via ## Error envelope -Every non-2xx response follows the same shape: +A non-2xx response carries a machine-readable `code` and a human-readable +message: ```json { "error": { "code": "PERMISSION_DENIED", "message": "Missing ai:approve on conversation 01H…", + "httpStatus": 403, "details": { "permission": "ai:approve", "subject": "01H…" } } } ``` -Common codes: `UNAUTHENTICATED`, `PERMISSION_DENIED`, -`VALIDATION_ERROR`, `NOT_FOUND`, `CONCURRENT_UPDATE`, -`RATE_LIMITED`, `INTERNAL`. +Some `/data` routes answer a flatter shape, with the message in `error` +and `code` beside it: + +```json +{ "error": "Record not found: support_ticket/01H…", "code": "RECORD_NOT_FOUND" } +``` + +Either way, branch on the code and not on the message. + +`code` is one closed SCREAMING_SNAKE vocabulary. Common codes: +`UNAUTHENTICATED`, `PERMISSION_DENIED`, `VALIDATION_FAILED` (with a +`fields` list naming each rejected value), `RECORD_NOT_FOUND`, +`RESOURCE_NOT_FOUND`, `CONCURRENT_UPDATE`, `RATE_LIMIT_EXCEEDED`, +`INTERNAL_ERROR`. A code your own application throws that is not in the +platform vocabulary arrives in `declaredCode` instead of `code`. ## Versioning diff --git a/content/docs/reference/rest-api.zh-Hans.mdx b/content/docs/reference/rest-api.zh-Hans.mdx deleted file mode 100644 index d456cc2..0000000 --- a/content/docs/reference/rest-api.zh-Hans.mdx +++ /dev/null @@ -1,199 +0,0 @@ ---- -title: REST API -description: ObjectOS 暴露的 HTTP 表面 —— 由元数据生成、按权限作用域控制、由 OpenAPI 描述。 -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -你声明的每个对象都会自动获得完整的 REST 端点集。每个 Action 都成为一个 `POST`。每个流程都成为对 `/flows/...` 的 `POST`。无需编写或部署单独的 API 层。 - -Base URL:`https:///api/v1`。 -OpenAPI spec 实时位于 [`/api/v1/openapi.json`](#discovery)。 - -## 认证 - -ObjectOS 使用 [Better Auth](https://www.better-auth.com/)。`/api/v1` 下所有路由都需要已认证会话,除非显式标为**公开**(见[公开表单](#public-forms))。认证方式在 `apps//auth.config.ts` 中配置 —— 通常是会话 cookie 或来自 `/api/v1/auth/sign-in` 的 bearer token。 - -权限**按路由、按记录**检查。下面标注了权限键(如 `ai:chat`)的路由会调用 `requirePermission(...)`;记录级访问由数据引擎内的 RBAC + 行级安全 + 字段级安全强制。 - -## 发现 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/api/v1` | GET | 服务发现 —— 列出所有已注册路由、版本、作用域模式 | -| `/api/v1/discovery` | GET | 上述发现文档的显式别名 | -| `/api/v1/openapi.json` | GET | 运行时中每个端点的 OpenAPI 3.0 spec | -| `/api/v1/search` | GET | 对所有对象上 searchable 字段的全文搜索 | - -## 限制 API 暴露面 - -自动生成的端点按对象逐个治理,因此你无需编写任何路由代码就能将某个对象移出 API 或将其设为只读: - -- **`apiEnabled`**(boolean,**默认 `true`**)—— 设为 `false` 可将该对象完全从 REST 面移除。对其路由的请求返回 404。 -- **`apiMethods`**(可选白名单)—— 设置后,仅列出的操作可达;其他任何操作都在 REST 层被拒绝。允许的取值:`get`、`list`、`create`、`update`、`delete`、`upsert`、`bulk`、`aggregate`、`history`、`search`、`restore`、`purge`、`import`、`export`。 - -```ts -// 一个只读的参考对象,永远不可通过 API 写入: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// 一个 API 永不暴露的内部对象: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -两者现在都在 REST 层强制执行(ADR-0049)—— 早期版本会解析这些属性但并不应用它们。 - -## 数据 —— `/api/v1/data/*` - -对你声明的每个对象提供 CRUD + 高级查询。`:object` 是对象的 `name`(snake_case)。 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/data/:object` | GET | 列表/查询 —— 将 `where`、`orderBy`、`limit`、`offset`、`cursor`、`expand`、`select` 作为查询参数传入 | -| `/data/:object/query` | POST | 高级查询 —— 带 `groupBy` + `aggregations` 的完整 ObjectQL 请求体(见 [ObjectQL](./objectql)) | -| `/data/:object/:id` | GET | 获取单条记录。支持 `?select=` 和 `?expand=` | -| `/data/:object` | POST | 创建。返回 `201` 与新记录 | -| `/data/:object/:id` | PATCH | 更新。传 `If-Match: ` header(或请求体中 `expectedVersion`)用于乐观并发 —— 冲突时返回 `409 CONCURRENT_UPDATE` | -| `/data/:object/:id` | DELETE | 删除。同 `If-Match` 规则 | -| `/data/:object/import` | POST | 批量导入(CSV 或 JSON)。请求体:`{ format, csv? \| rows?, mapping?, dryRun? }`。每请求最多 5,000 行 | -| `/data/:object/export` | POST | 将记录导出为 `csv` / `xlsx` / `pdf` / `json` | -| `/data/:object/:id/shares` | GET / POST | 单条记录共享 —— 列出/授予访问 | -| `/data/:object/:id/shares/:shareId` | DELETE | 撤销共享 | -| `/data/lead/:id/convert` | POST | Salesforce 风格的 lead 转换(CRM 模板)。请求体:`{ accountId?, contactId?, createOpportunity? }` | - -### 快速示例 - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI —— `/api/v1/ai/*` - -[AI Builder](/docs/build/ai-builder) 和你的 [Agent](/docs/build/agents) 所依赖的端点。 - -| 路径 | 方法 | 权限 | 用途 | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | 列出已配置提供商的可用模型 | -| `/ai/chat` | POST | `ai:chat` | 单次聊天补全(同步) | -| `/ai/chat/stream` | POST | `ai:chat` | 流式聊天(SSE) | -| `/ai/complete` | POST | `ai:chat` | 原始补全端点 | -| `/ai/conversations` | GET / POST | `ai:chat` | 列出/创建持久化对话 | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | 读取/删除对话 | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | 追加用户消息并运行 Agent | -| `/ai/pending-actions` | GET | `ai:read` | HITL 审批队列 —— AI 提议的变更 | -| `/ai/pending-actions/:id` | GET | `ai:read` | 检查排队中的变更 | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | 应用变更 | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | 丢弃 | - -`ai:read` 与 `ai:approve` 的拆分是让 AI Builder 对最终用户安全的关键安全原语。 - -## Actions —— `/api/v1/actions/*` - -你声明的每个 `*.action.ts` 都成为一个端点。 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/actions/:action` | POST | 调用 Action。请求体是 Action 的输入 schema。权限和输入校验来自 Action 声明 | - -每个 Action 也作为 `action_` 工具暴露给 AI —— 见 [Actions](/docs/build/interface/actions)。 - -## 流程 —— `/api/v1/flows/*` - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/flows/:flow/start` | POST | 启动一个流程。请求体是流程输入 | -| `/flows/:flow/runs/:runId` | GET | 运行中/已完成流程的状态 + 步骤输出 | -| `/flows/:flow/runs/:runId/cancel` | POST | 取消进行中的运行 | - -## 元数据 —— `/api/v1/meta/*` - -对运行时元数据的只读自省。供工具和 Console 使用。 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/meta` | GET | 所有元数据类型(`object`、`view`、`action`、`flow`、`agent`……) | -| `/meta/:type` | GET | 列出某类型项目。`?package=` 过滤 | -| `/meta/:type/:name` | GET | 获取单项。`?layers=true` 返回 3 态 diff 视图(base / package / overlay) | -| `/meta/:type/:name/references` | GET | 查找引用此项的所有元数据项 | -| `/meta/:type/:name/history` | GET | 该项的审计轨迹 | -| `/meta/:type/:section/:name` | GET / POST | 读取/upsert 分段的元数据项 | -| `/meta/:type/:section/:name` | DELETE | 删除分段项 | - -## 共享与审批 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | 管理共享规则 | -| `/sharing/rules/:idOrName/evaluate` | POST | 针对上下文 dry-run 一条规则 | -| `/approvals/processes` | GET / POST | 审批流程定义 | -| `/approvals/requests` | GET / POST | 提交/列出审批请求 | -| `/approvals/requests/:id/approve` | POST | 决策:批准 | -| `/approvals/requests/:id/reject` | POST | 决策:拒绝 | - -## 报表与搜索 - -| 路径 | 方法 | 用途 | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | 报表 CRUD | -| `/reports/:id/run` | POST | 执行报表 —— 返回聚合行 | -| `/reports/:id/schedule` | POST | 调度定期投递 | - -## 公开表单 - -仅有的**未认证**路由,通过 `FormView.sharing.allowAnonymous` 显式启用。 - -| 路径 | 方法 | 认证 | -|:--|:--|:--| -| `/forms/:slug` | GET | 公开 —— 返回表单 schema | -| `/forms/:slug/submit` | POST | 公开 —— 提交响应 | - -## 工具 - -| 路径 | 方法 | 权限 | 用途 | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | 通过已配置提供商发送邮件 | - -## 错误信封 - -每个非 2xx 响应遵循相同形状: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -常见 code:`UNAUTHENTICATED`、`PERMISSION_DENIED`、`VALIDATION_ERROR`、`NOT_FOUND`、`CONCURRENT_UPDATE`、`RATE_LIMITED`、`INTERNAL`。 - -## 版本管理 - -`/api/v1` 是**稳定** API。破坏性变更进入 `/api/v2`,两者在一个小版本期内并行服务。当前版本和弃用周期见 `GET /api/v1`。 - -## 参见 - -- [ObjectQL](./objectql) —— `/data/*` 使用的查询语言 -- [字段类型](./field-types) —— 数据可以呈现的形状 -- [Build → Actions](/docs/build/interface/actions) —— 声明自定义端点 -- [安全](./security) —— 每个路由运行前检查什么 diff --git a/content/docs/reference/rest-api.zh-Hant.mdx b/content/docs/reference/rest-api.zh-Hant.mdx deleted file mode 100644 index 1987ca2..0000000 --- a/content/docs/reference/rest-api.zh-Hant.mdx +++ /dev/null @@ -1,200 +0,0 @@ ---- -# @generated zh-Hant from zh-Hans -- do not edit. Edit the English source, then regenerate: pnpm --filter @objectos/docs gen:zh-hant -title: REST API -description: ObjectOS 暴露的 HTTP 表面 —— 由後設資料生成、按許可權作用域控制、由 OpenAPI 描述。 -translation: - source_sha: 0e34fa4eec3fd818962d58aac067b4c6d25995fc9fa675f6d2febee0a5bcadc1 - guide_rev: 1 - mode: auto ---- - -你宣告的每個物件都會自動獲得完整的 REST 端點集。每個 Action 都成為一個 `POST`。每個流程都成為對 `/flows/...` 的 `POST`。無需編寫或部署單獨的 API 層。 - -Base URL:`https:///api/v1`。 -OpenAPI spec 即時位於 [`/api/v1/openapi.json`](#discovery)。 - -## 認證 - -ObjectOS 使用 [Better Auth](https://www.better-auth.com/)。`/api/v1` 下所有路由都需要已認證會話,除非顯式標為**公開**(見[公開表單](#public-forms))。認證方式在 `apps//auth.config.ts` 中配置 —— 通常是會話 cookie 或來自 `/api/v1/auth/sign-in` 的 bearer token。 - -許可權**按路由、按記錄**檢查。下面標註了許可權鍵(如 `ai:chat`)的路由會呼叫 `requirePermission(...)`;記錄級訪問由資料引擎內的 RBAC + 行級安全 + 欄位級安全強制。 - -## 發現 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/api/v1` | GET | 服務發現 —— 列出所有已註冊路由、版本、作用域模式 | -| `/api/v1/discovery` | GET | 上述發現文件的顯式別名 | -| `/api/v1/openapi.json` | GET | 執行時中每個端點的 OpenAPI 3.0 spec | -| `/api/v1/search` | GET | 對所有物件上 searchable 欄位的全文搜尋 | - -## 限制 API 暴露面 - -自動生成的端點按物件逐個治理,因此你無需編寫任何路由程式碼就能將某個物件移出 API 或將其設為只讀: - -- **`apiEnabled`**(boolean,**預設 `true`**)—— 設為 `false` 可將該物件完全從 REST 面移除。對其路由的請求返回 404。 -- **`apiMethods`**(可選白名單)—— 設定後,僅列出的操作可達;其他任何操作都在 REST 層被拒絕。允許的取值:`get`、`list`、`create`、`update`、`delete`、`upsert`、`bulk`、`aggregate`、`history`、`search`、`restore`、`purge`、`import`、`export`。 - -```ts -// 一个只读的参考对象,永远不可通过 API 写入: -defineObject({ - name: 'exchange_rate', - apiMethods: ['get', 'list'], - // ...fields -}) - -// 一个 API 永不暴露的内部对象: -defineObject({ - name: 'sync_cursor', - apiEnabled: false, - // ...fields -}) -``` - -兩者現在都在 REST 層強制執行(ADR-0049)—— 早期版本會解析這些屬性但並不應用它們。 - -## 資料 —— `/api/v1/data/*` - -對你宣告的每個物件提供 CRUD + 高階查詢。`:object` 是物件的 `name`(snake_case)。 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/data/:object` | GET | 列表/查詢 —— 將 `where`、`orderBy`、`limit`、`offset`、`cursor`、`expand`、`select` 作為查詢引數傳入 | -| `/data/:object/query` | POST | 高階查詢 —— 帶 `groupBy` + `aggregations` 的完整 ObjectQL 請求體(見 [ObjectQL](./objectql)) | -| `/data/:object/:id` | GET | 獲取單條記錄。支援 `?select=` 和 `?expand=` | -| `/data/:object` | POST | 建立。返回 `201` 與新記錄 | -| `/data/:object/:id` | PATCH | 更新。傳 `If-Match: ` header(或請求體中 `expectedVersion`)用於樂觀併發 —— 衝突時返回 `409 CONCURRENT_UPDATE` | -| `/data/:object/:id` | DELETE | 刪除。同 `If-Match` 規則 | -| `/data/:object/import` | POST | 批次匯入(CSV 或 JSON)。請求體:`{ format, csv? \| rows?, mapping?, dryRun? }`。每請求最多 5,000 行 | -| `/data/:object/export` | POST | 將記錄匯出為 `csv` / `xlsx` / `pdf` / `json` | -| `/data/:object/:id/shares` | GET / POST | 單條記錄共享 —— 列出/授予訪問 | -| `/data/:object/:id/shares/:shareId` | DELETE | 撤銷共享 | -| `/data/lead/:id/convert` | POST | Salesforce 風格的 lead 轉換(CRM 模板)。請求體:`{ accountId?, contactId?, createOpportunity? }` | - -### 快速示例 - -```http -GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20 -Authorization: Bearer -``` - -```json -{ - "items": [ { "id": "...", "subject": "...", "status": "open" } ], - "total": 137, - "nextCursor": "eyJpZCI6IjAxSDA..." -} -``` - -## AI —— `/api/v1/ai/*` - -[AI Builder](/docs/build/ai-builder) 和你的 [Agent](/docs/build/agents) 所依賴的端點。 - -| 路徑 | 方法 | 許可權 | 用途 | -|:--|:--|:--|:--| -| `/ai/models` | GET | `ai:chat` | 列出已配置提供商的可用模型 | -| `/ai/chat` | POST | `ai:chat` | 單次聊天補全(同步) | -| `/ai/chat/stream` | POST | `ai:chat` | 流式聊天(SSE) | -| `/ai/complete` | POST | `ai:chat` | 原始補全端點 | -| `/ai/conversations` | GET / POST | `ai:chat` | 列出/建立持久化對話 | -| `/ai/conversations/:id` | GET / DELETE | `ai:chat` | 讀取/刪除對話 | -| `/ai/conversations/:id/messages` | POST | `ai:chat` | 追加使用者訊息並執行 Agent | -| `/ai/pending-actions` | GET | `ai:read` | HITL 審批佇列 —— AI 提議的變更 | -| `/ai/pending-actions/:id` | GET | `ai:read` | 檢查排隊中的變更 | -| `/ai/pending-actions/:id/approve` | POST | `ai:approve` | 應用變更 | -| `/ai/pending-actions/:id/reject` | POST | `ai:approve` | 丟棄 | - -`ai:read` 與 `ai:approve` 的拆分是讓 AI Builder 對終端使用者安全的關鍵安全原語。 - -## Actions —— `/api/v1/actions/*` - -你宣告的每個 `*.action.ts` 都成為一個端點。 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/actions/:action` | POST | 呼叫 Action。請求體是 Action 的輸入 schema。許可權和輸入校驗來自 Action 宣告 | - -每個 Action 也作為 `action_` 工具暴露給 AI —— 見 [Actions](/docs/build/interface/actions)。 - -## 流程 —— `/api/v1/flows/*` - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/flows/:flow/start` | POST | 啟動一個流程。請求體是流程輸入 | -| `/flows/:flow/runs/:runId` | GET | 執行中/已完成流程的狀態 + 步驟輸出 | -| `/flows/:flow/runs/:runId/cancel` | POST | 取消進行中的執行 | - -## 後設資料 —— `/api/v1/meta/*` - -對執行時後設資料的只讀自省。供工具和 Console 使用。 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/meta` | GET | 所有後設資料型別(`object`、`view`、`action`、`flow`、`agent`……) | -| `/meta/:type` | GET | 列出某型別專案。`?package=` 過濾 | -| `/meta/:type/:name` | GET | 獲取單項。`?layers=true` 返回 3 態 diff 檢視(base / package / overlay) | -| `/meta/:type/:name/references` | GET | 查詢引用此項的所有後設資料項 | -| `/meta/:type/:name/history` | GET | 該項的審計軌跡 | -| `/meta/:type/:section/:name` | GET / POST | 讀取/upsert 分段的後設資料項 | -| `/meta/:type/:section/:name` | DELETE | 刪除分段項 | - -## 共享與審批 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/sharing/rules` | GET / POST / DELETE | 管理共享規則 | -| `/sharing/rules/:idOrName/evaluate` | POST | 針對上下文 dry-run 一條規則 | -| `/approvals/processes` | GET / POST | 審批流程定義 | -| `/approvals/requests` | GET / POST | 提交/列出審批請求 | -| `/approvals/requests/:id/approve` | POST | 決策:批准 | -| `/approvals/requests/:id/reject` | POST | 決策:拒絕 | - -## 報表與搜尋 - -| 路徑 | 方法 | 用途 | -|:--|:--|:--| -| `/reports` | GET / POST / PATCH | 報表 CRUD | -| `/reports/:id/run` | POST | 執行報表 —— 返回聚合行 | -| `/reports/:id/schedule` | POST | 排程定期投遞 | - -## 公開表單 - -僅有的**未認證**路由,通過 `FormView.sharing.allowAnonymous` 顯式啟用。 - -| 路徑 | 方法 | 認證 | -|:--|:--|:--| -| `/forms/:slug` | GET | 公開 —— 返回表單 schema | -| `/forms/:slug/submit` | POST | 公開 —— 提交響應 | - -## 工具 - -| 路徑 | 方法 | 許可權 | 用途 | -|:--|:--|:--|:--| -| `/email/send` | POST | `email:send` | 通過已配置提供商傳送郵件 | - -## 錯誤信封 - -每個非 2xx 響應遵循相同形狀: - -```json -{ - "error": { - "code": "PERMISSION_DENIED", - "message": "Missing ai:approve on conversation 01H…", - "details": { "permission": "ai:approve", "subject": "01H…" } - } -} -``` - -常見 code:`UNAUTHENTICATED`、`PERMISSION_DENIED`、`VALIDATION_ERROR`、`NOT_FOUND`、`CONCURRENT_UPDATE`、`RATE_LIMITED`、`INTERNAL`。 - -## 版本管理 - -`/api/v1` 是**穩定** API。破壞性變更進入 `/api/v2`,兩者在一個小版本期內並行服務。當前版本和棄用週期見 `GET /api/v1`。 - -## 參見 - -- [ObjectQL](./objectql) —— `/data/*` 使用的查詢語言 -- [欄位型別](./field-types) —— 資料可以呈現的形狀 -- [Build → Actions](/docs/build/interface/actions) —— 宣告自定義端點 -- [安全](./security) —— 每個路由執行前檢查什麼 diff --git a/content/docs/resources/changelog.mdx b/content/docs/resources/changelog.mdx index 8f64874..51b54bb 100644 --- a/content/docs/resources/changelog.mdx +++ b/content/docs/resources/changelog.mdx @@ -5,12 +5,14 @@ description: How ObjectOS is versioned, what changes between releases, and what' ## Versioning policy -ObjectOS follows **[Semantic Versioning](https://semver.org/)**: `MAJOR.MINOR.PATCH`. +ObjectOS versions take the **[Semantic Versioning](https://semver.org/)** +shape, `MAJOR.MINOR.PATCH`, with one exception the table states: on the +v17 line a minor can break. | Version bump | What it means | What to do | |---|---|---| | **Patch** (`14.7.0 → 14.7.1`) | Bug fixes, no behavior change | Roll forward, no app changes needed | -| **Minor** (`14.6 → 14.7`) | New features, backward-compatible | Roll forward, optionally adopt new features | +| **Minor** (`17.4 → 17.5`) | New features — and, on the v17 line, breaking corrections too. Every 17.x release note says so in the same words: a minor "by version number, not by blast radius" | Read that release's upgrade checklist and the [version-specific steps](/docs/operate/upgrade#version-specific-steps) before rolling forward | | **Major** (`13 → 14`) | Breaking changes documented in release notes | Read the migration guide before upgrading | All `@objectstack/*` packages release together with a synchronized @@ -39,7 +41,7 @@ fixed calendar: Critical security fixes are backported to the current and previous major. Everything else lands on `main`. Dates attached to a specific major are published with that major's -[release notes](https://docs.objectstack.ai/docs/releases). +[release notes](https://objectstack.ai/docs/releases). ## Release notes @@ -48,7 +50,7 @@ Released ObjectOS versions and their CHANGELOG entries are published at: - **npm**: [`@objectstack/runtime`](https://www.npmjs.com/package/@objectstack/runtime) - **GitHub**: [github.com/objectstack-ai/objectstack/releases](https://github.com/objectstack-ai/objectstack/releases) - **Source CHANGELOG**: [`CHANGELOG.md`](https://github.com/objectstack-ai/objectstack/blob/main/CHANGELOG.md) -- **Long-form release notes**: [`RELEASE_NOTES.md`](https://github.com/objectstack-ai/objectstack/blob/main/RELEASE_NOTES.md) +- **Long-form release notes**: one page per release, with its upgrade checklist, at [objectstack.ai/docs/releases](https://objectstack.ai/docs/releases) — for the v17 line, [v17](https://objectstack.ai/docs/releases/v17) To be notified, watch [github.com/objectstack-ai/objectstack](https://github.com/objectstack-ai/objectstack) @@ -56,16 +58,209 @@ To be notified, watch ## Recent highlights +Every 17.x minor below changes behavior on a running deployment. The +operator's share of each upgrade checklist is on +[Upgrade and Rollback → Version-specific steps](/docs/operate/upgrade#version-specific-steps). + +### 17.7 + +`@objectstack` **17.7.0** (released 2026-10-06) is the current release of +the v17 line. It takes credentials out of the copies they had been made +into, and makes a row you cannot see answer as a row that is not there. +Full notes: +[objectstack.ai/docs/releases/v17/17-7](https://objectstack.ai/docs/releases/v17/17-7). + +> **Warning:** after upgrading, rotate every flow secret and the JWT +> signing keys, and re-mint private share links: copies made before the +> upgrade stay in the audit trail. A secret set on 17.7 cannot be read by +> an earlier release, so plan rollbacks with that in mind. See +> [Upgrade and Rollback → 17.7](/docs/operate/upgrade#objectstack-177). + +What moved: + +- **Credentials stop travelling in copies** (security) — a flow's inbound + `secret` and an `http` node's `signingSecret` move into a write-only + store, the audit trail stops recording credential fields, and credential + fields are masked in every write response, record event, webhook body + and flow trigger record. +- **Public forms open only on an explicit switch** (breaking) — a form is + served anonymously only when its `sharing` sets `enabled: true` beside + `allowAnonymous` and `publicLink`. A form that set only the last two + answers `404 FORM_NOT_FOUND`. See [Views](/docs/build/interface/views#public-forms). +- **A row you cannot read is not there** — a by-id update or delete of a + row the caller cannot read answers `404 RECORD_NOT_FOUND`, and an update + or delete by filter touches readable rows only. +- **Reads and writes return declared fields only** — a column no metadata + declares, such as a retired field's leftover, is no longer returned; + `os migrate unmapped-columns` reads it for a conversion. See + [CLI](/docs/reference/cli#database-migrations--os-migrate). +- **App code reaches stored metadata through the metadata API only** — + hook, action and job code no longer reads or writes `sys_metadata` + directly, and a flow node that writes it is refused. +- **Dates, times and numbers follow the language** — the four format + settings are retired, and a settings write that names one answers + `400`. See [Localization](/docs/configure/localization). +- **The in-memory engine is no longer a boot store**, and an + external sign-in no longer links itself to a local account whose email + is unverified. +- **Console (Studio)** — five objectui pin moves; the first fixes the four + known console issues of 17.6. + +### 17.6 + +`@objectstack` **17.6.0** (released 2026-10-02) decides access the same way +at every door: a caller with no permission set gets nothing, and field-level +security reaches every query. Full notes: +[objectstack.ai/docs/releases/v17/17-6](https://objectstack.ai/docs/releases/v17/17-6). + +What moved: + +- **A caller that resolves no permission set is refused every object** + (breaking, security) — it used to be admitted to every object no set + grants. An app-declared anonymous endpoint (`authRequired: false`) can + no longer read or write objects, and no supported channel grants + anonymous callers a permission set. Public forms keep working. +- **Field-level security reaches every query door** — cross-field + comparisons, the activity stream, the audit trail, approval snapshots + and analytics. The audit trail and the activity stream show a + non-system reader, administrators included, only rows about records that + reader can read; 17.7's `view_all_audit_log` capability, which platform + administrators hold by default, gives them the deletion and sign-out + trail back. See [Audit Logs](/docs/operate/audit-logs). +- **A packaged flow wins its name** — a flow saved in the deployment under + a packaged flow's name stops running, and the on/off toggle switches + packaged flows only. See [Flows](/docs/build/automation/flows#visual-builder). +- **Stored metadata bodies are redacted wherever they are copied** — run + `os migrate audit-metadata-bodies --apply` for the copies at rest. + Datasource metadata on `/api/v1/meta` needs `manage_platform_settings`. +- **「is empty」 also matches `''` and `[]`** in sharing rules and views, on + text and multi-value fields. +- **Dates follow one rule at every door** — a `datetime` names a year from + 1000 to 9999, and an import reads date cells only in ISO 8601, the + export's own shape, or year-first. +- **Shared picklists** — a new `picklist` metadata kind that fields + reference by name and other packages can extend. + +### 17.5 + +`@objectstack` **17.5.0** (released 2026-09-29) turns several defaults to +the safe side: scheduled work is off until a deployment asks for it, and +row-level security, analytics and record writes refuse what they cannot +honor. Full notes: +[objectstack.ai/docs/releases/v17/17-5](https://objectstack.ai/docs/releases/v17/17-5). + +> **Warning:** a deployment that upgrades and does nothing runs no +> scheduled flow and no packaged cron job, and a walled one has no +> platform administrator until `OS_PLATFORM_OWNER_EMAIL` names one. See +> [Upgrade and Rollback → 17.5](/docs/operate/upgrade#objectstack-175). + +What moved: + +- **Scheduled work is off until the deployment turns it on** (breaking) — + `OS_AUTOMATION_SCHEDULED_WORK_ENABLED` gates every time-triggered flow + and packaged cron job. See + [Environment Variables](/docs/reference/environment-variables#runtime-services). +- **Walled deployments take platform-admin standing only from + `OS_PLATFORM_OWNER_EMAIL`** (breaking) — an `admin_full_access` grant no + longer confers it. +- **The saved-report stack is removed** (breaking) — `/api/v1/reports` and + scheduled report delivery, with no successor. The `report` metadata kind, + datasets and analytics are unchanged. See [REST API](/docs/reference/rest-api#reports). +- **Row-level security fails closed** on a predicate it cannot enforce, + and a policy with no `check` now holds inserts and updates to its + `using`. +- **Analytics answers only callers who may read the object** — anyone + else is refused `403`, as on `/data`. +- **A decision takes its first matching branch** — `mode: 'inclusive'` + runs every true branch. A decision stored from Studio takes the new + meaning on upgrade. See [Flows](/docs/build/automation/flows#conditions-and-branches). +- **Written values are held to their declared type** — a `date` as + `YYYY-MM-DD`, a `datetime` in ISO 8601, a number in its JSON spelling; + anything else is `400 VALIDATION_FAILED`. +- **Validation rules can read one hop through a lookup**, and CEL gains + `current_user.can(object, verb)`. See + [Validation Rules](/docs/build/data/validation-rules#reading-a-related-record) + and [CEL](/docs/reference/cel#permission-checks). +- **A view with no declared page size shows 50 rows**, not 25. + +### 17.4 + +`@objectstack` **17.4.0** (released 2026-09-09) renames every duration key +to carry its unit and closes two quiet write paths. Full notes: +[objectstack.ai/docs/releases/v17/17-4](https://objectstack.ai/docs/releases/v17/17-4). + +What moved: + +- **A read-only field is stripped from a non-system create**, not only + from an update — reported in `droppedFields`. Seed one as the system. + See [Validation Rules](/docs/build/data/validation-rules). +- **Every duration key carries its unit in its name** (breaking) — + `timeout` becomes `timeoutMs`, `refreshInterval` becomes + `refreshIntervalSeconds`, and so on, with no aliases: an old spelling is + refused at parse, naming the key. +- **`positions` on the session is the security axis** — a Console gate + narrowed by a position now answers for the people who hold it. +- **A flow can refuse with a reason** — an `end` node with + `outcome: 'refused'` and a per-record `message`. See + [Flows](/docs/build/automation/flows#ending-a-run-completed-or-refused). +- **Analytics date ranges are a closed vocabulary** — a declared preset + or a `[start, end]` pair; anything else is `400`. +- **The multi-organization runtime is open source** — + `@objectstack/organizations` is a published package. + +### 17.3 + +`@objectstack` **17.3.0** (released 2026-09-04) is the largest release of +the v17 line. Full notes: +[objectstack.ai/docs/releases/v17/17-3](https://objectstack.ai/docs/releases/v17/17-3). + +What moved: + +- **Open self-registration must be declared** (breaking) — an app that + declares no audience posture is invite-only, and a sign-up without a + pending invitation is refused `403 SELF_REGISTRATION_CLOSED` (the first + account on a fresh install is exempt). +- **Sharing grants are tenant-scoped** — `sys_record_share` rows written + before 17.3 need an operator-run backfill. See + [Upgrade and Rollback → 17.3](/docs/operate/upgrade#objectstack-173). +- **An outage of the permission store fails loudly** (`503`), instead of + resolving as a user with no capabilities. +- **Auto-numbers are unique per organization**, and existing duplicates + block the new index: run `os migrate duplicates` first. +- **Compound metadata addressing is retired** — `/api/v1/meta/:type/:section/:name` + answers `404 ROUTE_NOT_FOUND`. See [REST API](/docs/reference/rest-api#metadata--apiv1meta). +- **Switching a flow on or off requires `manage_metadata`.** +- **SDK** — `client.projects.*` becomes `client.environments.*`, with no + aliases. + +### 17.2 + +`@objectstack` **17.2.0** (released 2026-08-23) stops writes that declared +a condition nobody evaluated from reading as conditional writes. Full +notes: [objectstack.ai/docs/releases/v17/17-2](https://objectstack.ai/docs/releases/v17/17-2). + +What moved: + +- **By-id writes stop dropping predicates** (breaking) — a by-id update or + delete whose `where` names more than `id` is refused unless it declares + `multi: true`, and one whose `data.id` and `where.id` disagree answers + `400 UPDATE_ID_MISMATCH`. +- **`http_request_errors_total` is retired** — a panel keyed on it reads a + flat zero; use `http_requests_total{status=~"5.."}`. +- **The external-datasource routes require a capability** — + `manage_platform_settings` or `manage_metadata`. +- **`os g agent` is retired** — author skills instead. + ### 17.1 -`@objectstack` **17.1.0** (released 2026-08-20) is the current release of +`@objectstack` **17.1.0** (released 2026-08-20) was the first minor of the v17 line. Where 17.0 made the *authorable surface* loud, 17.1 makes the runtime's answers loud: a flag that promised to revoke access now revokes it, a read that failed stops being served as an empty one, a flow that never dispatched stops being reported as a run that failed, and a credential that was never meant to be readable stops being served. Full notes: -[docs.objectstack.ai/docs/releases/v17](https://docs.objectstack.ai/docs/releases/v17). +[objectstack.ai/docs/releases/v17/17-1](https://objectstack.ai/docs/releases/v17/17-1). > **Warning:** 17.1.0 is a minor by version number, not by blast radius. > Several of its security corrections change who can read or write on an @@ -103,24 +298,13 @@ What moved: users and the CSV/XLSX export route all see the same value; a masked caller cannot filter, sort, group or aggregate on the field. See [Field-Level Security](/docs/configure/permissions/field-level-security). -- **`sys_audit_log` can answer "who viewed this record"** — the ledger - covered writes only; it gains a `read` action, its writer, and a - `record_views` list view. Record-detail reads only, per-object opt-in - with no global switch, batched off the request path, and **never any - field values** — read auditing runs ahead of field masking, so copying - values in would mint a plaintext copy of exactly what field-level - security withholds. See [Audit Logs](/docs/operate/audit-logs). -- **Read-only approval visibility, per object, default off** — an object - can be named so that a user who can read a business record may also see - that record's approval requests and full action history. Omitted or - empty leaves visibility exactly as it is; on an object you do name it is - not a no-op — a supervisor who holds full read but never appears in the - approval used to get an empty list, and now sees the request row, every - actor and decision, the action's comment text (意见正文), and decision - attachments. The tier is read-only and introduces no new permission - concept: the service reads the record as the caller, so ordinary CRUD - and record access decide. See - [Approvals](/docs/build/automation/approvals). +- **Two open-runtime capabilities that ObjectOS does not expose** — + 17.1 added record-view auditing (a `read` action in `sys_audit_log`) and + a read-only tier that lets a record's readers see its approval requests. + Each is switched on only by a constructor option — `AuditPlugin`'s + `readAudit`, the approval service's `recordReaderVisibleObjects` — that + the ObjectOS runtime does not set, so neither is available on an + ObjectOS deployment. - **All three flow doors answer one honest status table** — a refused dispatch stops being reported as a failed run: `404` not found, `409` `FLOW_DISABLED`, `422` `FLOW_NO_START_NODE`, and `400` `FLOW_FAILED` @@ -160,7 +344,7 @@ What moved: `@objectstack` **17.0.0** (released 2026-08-14) is a truth-telling release: where 16.0 made *declared metadata* honest, 17.0 does the same for the surfaces around it. Full notes: -[docs.objectstack.ai/docs/releases/v17](https://docs.objectstack.ai/docs/releases/v17). +[objectstack.ai/docs/releases/v17/17-0](https://objectstack.ai/docs/releases/v17/17-0). What moved: - **Node.js 22 is the floor** (breaking) — `engines.node` moves from @@ -232,7 +416,7 @@ 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). +[objectstack.ai/docs/releases](https://objectstack.ai/docs/releases). What moved: - **`tenantId` alias removed from hook/action `ctx`** (16.0, breaking) — @@ -304,7 +488,7 @@ 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 permission metadata before rolling forward from ≤ 12.x**. Full notes: -[docs.objectstack.ai/docs/releases](https://docs.objectstack.ai/docs/releases). +[objectstack.ai/docs/releases](https://objectstack.ai/docs/releases). What moved in 14.x: - **ADR-0090 vocabulary convergence completed** (14.0, breaking) — @@ -525,7 +709,8 @@ throughout. Affected: ## Upgrading See [Upgrade and Rollback](/docs/operate/upgrade) for the mechanical -steps. Pre-flight: +steps, and its [version-specific steps](/docs/operate/upgrade#version-specific-steps) +for every release you cross. Pre-flight: 1. Read the CHANGELOG entry for every minor between your current and target version.