docs: P1 wave 2 — RLS, translations, flow expressions/API, approval lifecycle#3113
Merged
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Jul 17, 2026
Closed
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
审计修复第三批(P1 wave 2),补齐四个功能点缺口。每页先派源码调研 agent(只收带 file:line 证据的事实),结果修正了审计自身的三处判断——这正是"文档不撒谎"该有的做法。
1. 新增
permissions/rls.mdxoperation取值互相矛盾(select/all/update)。实际不矛盾:rls.zod.ts:111枚举五值全合法,三处只是不同用例。故本页不"裁决",而是讲何时选哪个。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.mdx12 项 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两节 + 三处示例 bugcondition是裸 CEL、decisionexpression必须{花括号}、字段值是插值。顺手修掉该页两个真 bug:expression: 'order_amount > 10000'(缺花括号)会退化成字符串比较恒真,永远走第一分支且不报错;due_date: 'TODAY() + 7'(缺花括号)把字面量文本写进日期字段。另object:→objectName:(canonical;别名会被发布门拒绝)。POST /api/v1/automation/:name/trigger,含 runs/resume/screen、isInput与 body 映射、身份转发与 runAs、以及success:true+skipped:true(condition_not_met) 陷阱。4.
automation/approvals.mdx生命周期 walkthroughsys_approval_request自带my_pending视图 + Setup→Approvals 导航 +GET /approvals/requests?approverId=&status=(approverId 可传 user id/email/role:x多值)。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