Skip to content

docs: P1 wave 2 — RLS, translations, flow expressions/API, approval lifecycle - #3113

Merged
os-zhuang merged 1 commit into
mainfrom
docs/p1-wave2
Jul 17, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
docs/p1-wave2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景

审计修复第三批(P1 wave 2),补齐四个功能点缺口。每页先派源码调研 agent(只收带 file:line 证据的事实),结果修正了审计自身的三处判断——这正是"文档不撒谎"该有的做法。

1. 新增 permissions/rls.mdx

  • 审计以为三处文档 operation 取值互相矛盾(select/all/update)。实际不矛盾:rls.zod.ts:111 枚举五值全合法,三处只是不同用例。故本页不"裁决",而是讲何时选哪个。
  • 真正的地雷(全站零文档):RLS 表达式只有 4 种形式能编译(col == current_user.prop / col == 'literal' / col IN (current_user.array) / 1 = 1);AND/OR/比较符/IS NULL/LIKE/子查询/NOW() 全部拒绝,写错即 fail-closed 静默拒绝。补"用多条策略代替 AND/OR"的正解。
  • 另含:裸列名(无 record. 前缀,与 UI/flow 方言不同)、current_user.* 变量表(及为何不暴露 name——重名=越权)、叠加语义(Layer0 租户墙 AND Layer1;层内策略 OR)、四条 fail-closed 路径、"无适用策略=不限制"陷阱、/security/explain 验证。

2. 新增 ui/translations.mdx

12 项 landing 能力里唯一指南层完全空白的一项。bundle 形态(locale map)、注册、可翻译面速查表、locale 解析(Accept-Language → ?locale= → default;zh-CN→zh→en→字面量,永不抛错)、三种文件组织、os i18n extract/check --strict --threshold 作 CI 门。诚实边界:validationMessages 无 runtime 消费者、ICU 未实现、文件 bundle(objects.)与运行时 translation 元数据(o.)两套 shape 未桥接(故建议以文件授权)。

3. automation/flows.mdx 两节 + 三处示例 bug

  • Expressions in flows:三方言确实并存且按位置决定——start/edge condition 是裸 CEL、decision expression 必须 {花括号}、字段值是插值。顺手修掉该页两个真 bug:expression: 'order_amount > 10000'(缺花括号)会退化成字符串比较恒真,永远走第一分支且不报错;due_date: 'TODAY() + 7'(缺花括号)把字面量文本写进日期字段。另 object: → objectName:(canonical;别名会被发布门拒绝)。
  • Run a flow via API:补上该页此前只提及、从不给出的路径 POST /api/v1/automation/:name/trigger,含 runs/resume/screen、isInput 与 body 映射、身份转发与 runAs、以及 success:true + skipped:true(condition_not_met) 陷阱。

4. automation/approvals.mdx 生命周期 walkthrough

  • 审计以为待办队列缺失。实际存在:sys_approval_request 自带 my_pending 视图 + Setup→Approvals 导航 + GET /approvals/requests?approverId=&status=(approverId 可传 user id/email/role:x 多值)。
  • 真正的缺口:开启审批不发任何通知(reminder/escalation/return 有 topic,初始请求没有)——如实写明 + 两条出路(flow 加 notify 节点 / 装 messaging 用 remind)。
  • 另含:approve/reject 端点与权限判定(actor 必须在 pending_approvers)、**unanimous 下非最后一位 approve 不终结(仍 pending)**的易错点、type:'role' ≠ 职位(是 better-auth 层级,写 sales_manager 静默无人)、字段镜像与记录锁、escalation 四种 action 及其"需 job 服务否则 display-only"前置、6 条错误码映射。

验证

pnpm --filter @objectstack/docs build ✅(exit 0)。(types:check 首跑因 .source 生成顺序偶发失败,重跑通过——既有 flake,与本改动无关。)

🤖 Generated with Claude Code

…ifecycle

Four audit gaps closed, all grounded in source research. Three of them
corrected the audit's own assumptions — noted below.

New: permissions/rls.mdx
- The audit believed three pages contradicted each other on `operation`.
  They don't: 'select' | 'insert' | 'update' | 'delete' | 'all' are all
  valid. The real undocumented landmine is the grammar — only four forms
  compile (col == current_user.prop, col == literal, col IN (array),
  1 = 1); AND/OR/comparisons/IS NULL/LIKE/subqueries/NOW() are rejected,
  and a non-compiling policy denies everything.
- Also documents: bare column names (no record. prefix), the context
  variable set (and why current_user.name is withheld), Layer 0 AND
  Layer 1 with policies OR-ing inside a layer, the four fail-closed
  paths, "no applicable policy = no restriction" (the trap), and
  verification via /security/explain.

New: ui/translations.mdx — the one landing-promise capability with zero
guide coverage. Bundle shape, registration, translatable surfaces,
locale resolution (Accept-Language → ?locale= → default; zh-CN → zh → en
→ literal), file organization, os i18n extract/check as a CI gate, and
honest limits (validationMessages has no consumer, ICU unimplemented,
file vs runtime bundle shapes unbridged).

automation/flows.mdx
- New "Expressions in flows": the three dialects are real and differ by
  position — start/edge conditions are bare CEL, decision expressions
  need {braces}, field values are interpolation. Fixed two example bugs
  this exposed: `expression: 'order_amount > 10000'` (no braces) degrades
  to a string compare that is ALWAYS TRUE, and `due_date: 'TODAY() + 7'`
  writes the literal text. Also `object:` → `objectName:` (canonical;
  the alias is publish-gate rejected).
- New "Run a flow via API": the endpoint the page previously referenced
  without ever naming (POST /api/v1/automation/:name/trigger), plus
  runs/resume/screen, input binding via isInput, identity forwarding,
  and the success:true + skipped:true trap.

automation/approvals.mdx — end-to-end lifecycle Steps walkthrough. The
audit called the approver queue missing; it exists (sys_approval_request
my_pending view + Setup nav + GET /approvals/requests). The actual gap is
that opening a request notifies nobody — now stated with the two
workarounds. Adds approve/reject endpoints, the unanimous
not-finalized-yet trap, type:'role' ≠ position, field mirroring, real
escalation (with its job-service precondition), and the error table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 10:10am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/l labels Jul 17, 2026
@os-zhuang
os-zhuang merged commit 25a19be into main Jul 17, 2026
14 of 15 checks passed
@os-zhuang
os-zhuang deleted the docs/p1-wave2 branch July 17, 2026 10:10
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…g the ratchet

#3113's lifecycle walkthrough re-stated a warning the page already makes
better 80 lines up (§3 The approval node: position vs the membership tier,
including the `os lint` rule that flags it). The duplicate pushed the
reserved-word count (ADR-0090 D3) from 5 to 9, so check-role-word failed
on every PR cut from main since — #3121 tripped over it.

Removing the duplicate and pointing at the original brings the count back
to exactly the baselined 5: **no baseline change needed**. The walkthrough
keeps a one-line pointer that names the approver kinds and links to the
existing callout, which is what a reader needs at that point anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…g the ratchet (#3130)

#3113's lifecycle walkthrough re-stated a warning the page already makes
better 80 lines up (§3 The approval node: position vs the membership tier,
including the `os lint` rule that flags it). The duplicate pushed the
reserved-word count (ADR-0090 D3) from 5 to 9, so check-role-word failed
on every PR cut from main since — #3121 tripped over it.

Removing the duplicate and pointing at the original brings the count back
to exactly the baselined 5: **no baseline change needed**. The walkthrough
keeps a one-line pointer that names the approver kinds and links to the
existing callout, which is what a reader needs at that point anyway.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
The README opened with a category label ("A metadata protocol and
TypeScript toolkit") and spent its first 100 lines on architecture layers
and protocol theory — none of it the point a visitor is here for. It now
mirrors the landing page (objectstack.ai, #3090) and tells the story the
project is actually about:

- hero: "AI writes the app. ObjectStack is what it writes." + the
  correctness chain (typed → validated → reviewed → governed)
- The loop: create → describe to Claude Code → preview in the browser;
  requirement changes run the same loop
- What can it actually build? — the 12 metadata capabilities, framed
  against the alternative (an agent on an empty repo writes a one-off
  codebase; here it composes from a vocabulary the runtime already runs)
- Why the mistakes don't ship — the four gates, why TypeScript was the
  right host language (errors become corrective text the agent fixes
  itself), and the context-window argument
- Your app is AI-operable, for free — MCP in three lines
- This repo / architecture / ObjectOS cross-link, compressed to the tail

Reference sections (packages, CLI, scripts) are kept as-is. Drops the
stale v4.0.1 version badge (CLI is 15.x — the badge had drifted three
majors). Quick Start's duplicate scaffold snippet folds into the loop.

Also fixes a third missing-braces decision expression in flows.mdx:155
(same class as the two fixed in #3113 — without braces the comparison
degrades to a string compare that never matches as intended).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 17, 2026
The README opened with a category label ("A metadata protocol and
TypeScript toolkit") and spent its first 100 lines on architecture layers
and protocol theory — none of it the point a visitor is here for. It now
mirrors the landing page (objectstack.ai, #3090) and tells the story the
project is actually about:

- hero: "AI writes the app. ObjectStack is what it writes." + the
  correctness chain (typed → validated → reviewed → governed)
- The loop: create → describe to Claude Code → preview in the browser;
  requirement changes run the same loop
- What can it actually build? — the 12 metadata capabilities, framed
  against the alternative (an agent on an empty repo writes a one-off
  codebase; here it composes from a vocabulary the runtime already runs)
- Why the mistakes don't ship — the four gates, why TypeScript was the
  right host language (errors become corrective text the agent fixes
  itself), and the context-window argument
- Your app is AI-operable, for free — MCP in three lines
- This repo / architecture / ObjectOS cross-link, compressed to the tail

Reference sections (packages, CLI, scripts) are kept as-is. Drops the
stale v4.0.1 version badge (CLI is 15.x — the badge had drifted three
majors). Quick Start's duplicate scaffold snippet folds into the loop.

Also fixes a third missing-braces decision expression in flows.mdx:155
(same class as the two fixed in #3113 — without braces the comparison
degrades to a string compare that never matches as intended).

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…l` (#3133)

`ApproverType.role` was the last platform surface projecting the word
ADR-0090 D3 reserved. Renaming it also unbreaks `lint.yml` on main: the
role-word ratchet has been red since #3113 (approvals.mdx 5 → 9), and the
docs stop needing the word once the type is spelled correctly.

D3's exception does not cover this enum. It protects better-auth's own
`sys_member.role` COLUMN — third-party schema we cannot rename. `ApproverType`
is ours: an authoring surface, i.e. the *projection*, which D3 says is spelled
`org_membership_level` and labelled "organization membership", never "role".

The sentence licensing the leak is itself false. ADR-0090 D3:203 claims
`sys_member.role` is "already relabelled `org_membership_level` in the platform
projection (ADR-0057 D7)" — but `org_membership_level` appeared nowhere in the
codebase (one comment in position.zod.ts), and ADR-0057 D7:335 lists that
relabel under "Deferred (evidence-gated, P4)". The projection never landed, so
the word reached authors.

The name manufactured a silent failure ("hotcrm class"): every sibling surface
renamed to `position` (`sys_role`, `ShareRecipientType.role`, `ctx.roles[]`),
so `{ type: 'role', value: 'sales_manager' }` reads as a position's legacy
spelling. It resolves against the membership tier, finds no member row, falls
back to an inert `role:sales_manager` literal, and the request waits forever.
Repo-wide, `type: 'role'` had ZERO real callers — only lint tests and the docs
warning that exists to undo the confusion the name creates.

- spec: `ApproverType` gains `org_membership_level`; `role` kept as a
  deprecated alias for one window so a published 15.x flow keeps loading.
  `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` are the single
  source for the mapping (runtime and lint both read it).
- plugin-approvals: resolves on the canonical type, warns on the deprecated
  spelling, `expandRoleUsers` → `expandMembershipTierUsers`. The `type:value`
  fallback literal deliberately keeps the AUTHORED spelling — 15.x wrote
  `role:<v>` into `sys_approval_approver` / `pending_approvers`, and
  canonicalising it here would orphan every stored slot.
- lint: `approval-role-not-membership-tier` →
  `approval-approver-not-membership-tier` (the rule id carried the word too),
  plus `approval-approver-type-deprecated`. Mutually exclusive: a bad VALUE
  wins, because prescribing `org_membership_level` for a position name is
  wrong advice — the fix there is `position`.
- docs/skill/reference + role-word baseline ratcheted DOWN (approvals.mdx
  5 → 1, automation SKILL.md 3 → 1); api-surface snapshot regenerated
  (0 breaking, 2 added).

Studio still offers "Role" and its picker calls `client.list('role')` on a
metadata type D3 deleted — that picker is already dead, and the dropdown is
objectui's own hardcoded copy of this enum. Tracked as objectui follow-up in
degrades to free text (strictly better than a picker that lists nothing).

Closes #3133. Supersedes #3120 and #3122, which only deleted the duplicated
docs callout without touching the name that causes the confusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
xuyushun441-sys pushed a commit that referenced this pull request Jul 17, 2026
…l` (#3133)

`ApproverType.role` was the last platform surface projecting the word
ADR-0090 D3 reserved. Renaming it also unbreaks `lint.yml` on main: the
role-word ratchet has been red since #3113 (approvals.mdx 5 → 9), and the
docs stop needing the word once the type is spelled correctly.

D3's exception does not cover this enum. It protects better-auth's own
`sys_member.role` COLUMN — third-party schema we cannot rename. `ApproverType`
is ours: an authoring surface, i.e. the *projection*, which D3 says is spelled
`org_membership_level` and labelled "organization membership", never "role".

The sentence licensing the leak is itself false. ADR-0090 D3:203 claims
`sys_member.role` is "already relabelled `org_membership_level` in the platform
projection (ADR-0057 D7)" — but `org_membership_level` appeared nowhere in the
codebase (one comment in position.zod.ts), and ADR-0057 D7:335 lists that
relabel under "Deferred (evidence-gated, P4)". The projection never landed, so
the word reached authors.

The name manufactured a silent failure ("hotcrm class"): every sibling surface
renamed to `position` (`sys_role`, `ShareRecipientType.role`, `ctx.roles[]`),
so `{ type: 'role', value: 'sales_manager' }` reads as a position's legacy
spelling. It resolves against the membership tier, finds no member row, falls
back to an inert `role:sales_manager` literal, and the request waits forever.
Repo-wide, `type: 'role'` had ZERO real callers — only lint tests and the docs
warning that exists to undo the confusion the name creates.

- spec: `ApproverType` gains `org_membership_level`; `role` kept as a
  deprecated alias for one window so a published 15.x flow keeps loading.
  `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` are the single
  source for the mapping (runtime and lint both read it).
- plugin-approvals: resolves on the canonical type, warns on the deprecated
  spelling, `expandRoleUsers` → `expandMembershipTierUsers`. The `type:value`
  fallback literal deliberately keeps the AUTHORED spelling — 15.x wrote
  `role:<v>` into `sys_approval_approver` / `pending_approvers`, and
  canonicalising it here would orphan every stored slot.
- lint: `approval-role-not-membership-tier` →
  `approval-approver-not-membership-tier` (the rule id carried the word too),
  plus `approval-approver-type-deprecated`. Mutually exclusive: a bad VALUE
  wins, because prescribing `org_membership_level` for a position name is
  wrong advice — the fix there is `position`.
- docs/skill/reference + role-word baseline ratcheted DOWN (approvals.mdx
  5 → 1, automation SKILL.md 3 → 1); api-surface snapshot regenerated
  (0 breaking, 2 added).

Studio still offers "Role" and its picker calls `client.list('role')` on a
metadata type D3 deleted — that picker is already dead, and the dropdown is
objectui's own hardcoded copy of this enum. Tracked as objectui follow-up in
degrades to free text (strictly better than a picker that lists nothing).

Closes #3133. Supersedes #3120 and #3122, which only deleted the duplicated
docs callout without touching the name that causes the confusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 1ec9eb8d Deployed Jul 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant