Skip to content

fix(analytics): make the read-scope auto-bridge order-independent + first real e2e RLS gate (#3618) - #3619

Merged
os-zhuang merged 1 commit into
mainfrom
test/analytics-rls-e2e-3597
Jul 27, 2026
Merged

fix(analytics): make the read-scope auto-bridge order-independent + first real e2e RLS gate (#3618)#3619
os-zhuang merged 1 commit into
mainfrom
test/analytics-rls-e2e-3597

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes#3618. 端到端补上 #3597 我明确说过没做的那一环。

起因:门先红了,但不是为 #3597 红的

#3597 写真实栈端到端门时,member 的聚合在两条 strategy 上都返回全表 5 行 —— 连 #3601 刚修好的 NativeSQL 路径也漏。前提检查是过的(GET /data/rls_note 只返回 member 自己的 2 条),说明 owner 策略在普通读路径上生效。

日志给出了答案:

WARN [Analytics] No getReadScope configured and no "security" service with getReadFilter found
INFO [Analytics] Auto-bridged executeAggregate → "data" service
INFO [Analytics] Auto-bridged executeRawSql → "data" service

另外两个桥都接上了,只有 read-scope 桥没接

缺陷:代码和它自己的注释矛盾

plugin.ts:277-306 注释写着 "resolved at call time so plugin-init order does not matter",但:

if(trySecurity()){// ← 卡在 init 时刻的探测getReadScope=(object,context)=>trySecurity()?.getReadFilter(object,context);}

闭包是懒的,可赋值本身被 init 时刻的探测卡住了。security 晚注册 ⇒ getReadScopeundefinedcallCtxanalytics-service.ts:332 直接 return this.baseCtx ⇒ 两条 strategy 都不加 scope。

两个兄弟桥都是对的,只有它不是 —— executeRawSql 的注释甚至明写 "Always wire the bridge... plugin-init order does not matter"

影响面(诚实划界)

生产 CLI 不受影响:os serve 里 SecurityPlugin 在 serve.ts:1658,analytics 由 capability resolver 在其之后加载,顺序正确。

受影响的是:

  1. 自建 kernel 的 embedder —— 框架按库分发,谁把 analytics 排在 security 前面就静默失去 analytics RLS,唯一提示是一条措辞还偏小的 WARN。
  2. 仓库自己的 bootStack —— analytics 在 harness.ts:159,security 在 :232整个 dogfood/verify 套件的 analytics RLS 一直是关的,那里写的任何 analytics RLS 断言都是空过的。

修复

getReadScope 无条件接线,探测只用来决定日志措辞 —— 与两个兄弟桥一致。顺带改掉 WARN 的错误措辞(原文说只影响 "raw-SQL path" 的 "joined objects";实际没 provider 时所有路径所有对象都不加 scope)。

新增的门

packages/qa/dogfood/test/analytics-rls.dogfood.test.ts —— 第一个真正跑通 getReadScope → security.getReadFilter 自动桥的测试。此前所有 analytics RLS 测试都是往手搓的 AnalyticsService 注入假 getReadScope,所以这条桥零覆盖,这也正是缺陷能活下来的原因。

断言的是真实非管理员经 HTTP 拿到的行,不是某个 filter 对象:

  • ObjectQL 路径(带 dateGranularity,NativeSQL 会声明失败)→ member 只数到自己的 2 行
  • NativeSQL 路径(无 granularity)→ 同样 2 行
  • 两条路径互相一致
  • 地基断言:系统上下文下全表确有 5 行 —— 排除"member 数到 2 是因为表里本来就只有 2 行"这种假绿

用 owner-scoped(created_by)而非 org-scoped 是刻意的:没有企业版 org-scoping 服务时,wildcard organization_id 策略会被剥掉,门会静默常绿。owner 策略引用 current_user.id,单租户 boot 下依然生效。

验证

  • 新门 4/4 绿;回退本 PR 的 plugin.ts 改动 → 红 3 个;回退 fix(analytics): enforce read scope on the ObjectQL aggregate path (#3597) #3601 的 strategy 改动 → ObjectQL 那条红。
  • @objectstack/service-analytics179 passed
  • 完整 dogfood 门:61 files / 355 tests 全绿(本地全仓 build 后跑的)。
  • tsc --noEmit 未引入新错误(该包 2 个存量错误在我没碰的测试文件里)。

待核

cloud 是独立仓库,我没检查它的插件注册顺序。若任何已发布 composition 用了不利顺序,#3618 应升级到 p1。

🤖 Generated with Claude Code

)
`getReadScope` was only wired when the `security` service already existed
at this plugin's init(). The closure resolved lazily, but the ASSIGNMENT
was gated on an init-time probe — so a kernel registering
AnalyticsServicePlugin before the security plugin got no read-scope
provider at all, and every analytics strategy ran unscoped behind nothing
but a WARN.
Both sibling bridges (executeAggregate, executeRawSql) wire
unconditionally and resolve at call time, and this one's own comment
claimed it did too. Now it does; the probe only picks the log wording.
`os serve` registers security first, so the CLI path was already correct.
The exposure was embedders composing their own kernel — and this repo's
own bootStack harness, which registers analytics at harness.ts:159 and
security at :232. That means the entire dogfood/verify suite has been
running with analytics RLS silently disabled, so any analytics RLS
assertion written there passed vacuously.
Also corrects the WARN text: with no provider nothing is scoped on any
path or object, not just "the raw-SQL path" / "joined objects".
Adds analytics-rls.dogfood.test.ts — an owner-scoped RLS fixture driven
over real HTTP as a real non-admin, asserting the rows a member's
aggregate actually returns rather than inspecting a filter object. This
is the first test to exercise the getReadScope -> security.getReadFilter
auto-bridge at all. Found while writing the end-to-end gate for #3597:
the gate went red for THIS reason, not that one.
Reverting this fix turns 3 of its 4 cases red; reverting the #3597
strategy fix turns the ObjectQL case red.
Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercelBot commented Jul 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredJul 27, 2026 9:55am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): packages/qa, packages/services.

8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/webhooks.mdx(via packages/services)
  • content/docs/kernel/runtime-services/audit-service.mdx(via packages/services)
  • content/docs/kernel/runtime-services/index.mdx(via packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx(via packages/services)
  • content/docs/permissions/authorization.mdx(via packages/qa)
  • content/docs/permissions/delegated-administration.mdx(via packages/qa)
  • content/docs/plugins/packages.mdx(via packages/services)
  • content/docs/protocol/kernel/i18n-standard.mdx(via packages/services)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(analytics): read-scope 自动桥是插件顺序依赖的 — security 晚注册则 analytics RLS 静默全关

1 participant

@os-zhuang