Repository navigation
新增 /:objectName/data 参数化裸数据路由:URL 过滤 + 不绑定视图,现有菜单/路由零破坏 #2251
Description
Activity
设计讨论补充两组约束(来自 Odoo 模型对照与数据保密性评审):
补充验收标准:展示类型切换保留过滤状态
裸表面(
_all)上切换展示类型(grid → kanban 等,不点任何具名视图 Tab)时,URL 中的filter[...]/uf_*状态必须原样保留——数据范围与展示类型正交(Odoo 的 view-mode 切换语义)。补充章节:安全模型(裸表面 ≠ 绕过授权)
原则:视图不是安全边界。裸表面显示的是「行级权限过滤后你有权看到的全量」,不是「所有数据」。保密性由权限层承担(参照
@object-ui/permissions已有的三层模型:对象级 actions /rowPermissions.filter/fieldPermissions,见packages/permissions/src/evaluator.ts),视图 filter 只是展示配置。必须满足:
- 路由门禁:无对象 read 权限时,裸路由与
/view/:viewId返回 403 视图,不渲染空列表。 - 合成列过字段级权限:
_all的默认列自动生成自对象定义,必须先经fieldPermissions过滤——精选视图故意省略的敏感列不得因列合成而暴露(方案 A 相对现状唯一新增的暴露面)。 - 不可读字段禁止参与过滤/排序/分组:防 filter-oracle 泄露(如
filter[salary][gte]=...通过结果集反推数值)。服务端丢弃不可读字段上的谓词;筛选栏(defaultUserFilters/自动生成)同样不提供这些字段。 - 行级过滤与入口过滤呈现区分:行级权限 filter 是隐形底线(不出现在筛选栏、不可移除,Odoo
ir.rule式);入口/URL 条件渲染为可见、可删除的 chip(优于 Odoo action domain 的隐形设计)。 - 强制点在服务端:
rowFilter与字段级限制由 framework 在 API 查询层注入;UI 权限包仅做同构体验裁剪,不作为防线。
相应地,验收标准追加:以低权限角色访问
/:objectName?filter[...],返回数据不越过行级 filter;敏感字段不出现在合成列与筛选栏;对不可读字段的 URL 谓词被服务端忽略(有测试覆盖)。
Generated by Claude Code
- 路由门禁:无对象 read 权限时,裸路由与
- changed the title
[-]对象路由语义重构:裸对象路由 = 全量数据 + URL 过滤的全功能表面,视图绑定仅走 /view/:viewId(方案 A)[/-][+]新增 /:objectName/data 参数化裸数据路由:URL 过滤 + 不绑定视图,现有菜单/路由零破坏[/+]on Jul 5, 2026 - added a commit that references this issue
on Jul 5, 2026 objectui 侧实现已随 #2255 合入
main(squashb8f158c),决策记录沉淀为 ADR-0055(docs/adr/0055-parameterized-bare-data-surface.md)。已落地:
/:objectName/data路由 +ObjectDataPage(可删 chip、无回写、另存为视图、权限门禁与字段级裁剪、展示切换保留过滤)、NavigationItem.filters+resolveHref分支(12 单测)、i18n(en/zh)、app-composition 两份指南与 console 路由文档更新。本 issue 保持 open,剩余为 framework 仓联动项:
-
@objectstack/spec同步ObjectNavItem.filters - 服务端强制:行级 filter 注入 + 丢弃不可读字段谓词(anti filter-oracle)
- 富操作符语法
filter[<field>][<op>]=(可选,后置) - 带种子数据环境的实机验收(本次环境无后端,已在 feat(app-shell): add /:objectName/data parameterized bare data surface (#2251) #2255 注明)
Generated by Claude Code
-
- added a commit that references this issue
on Jul 5, 2026 进度更新:framework 侧 spec 同步已完成。
- ✅
@objectstack/spec同步ObjectNavItem.filters— framework#2626 已合并(2bb193d,含 2 个 schema 测试、objectstack-uiskill 三模式决策表、content/docs/ui/apps.mdx、minor changeset) - ✅ app-composition eval 补充
filters正向用例(防生成退化)— docs(skills): add filters-slice eval to app-composition (#2251 follow-up) #2265 已合并(c19ddf0)
剩余:
- 服务端强制:行级 filter 注入 + 丢弃不可读字段谓词(anti filter-oracle)— framework API 层,建议单独立项
- 富操作符语法
filter[<field>][<op>]=(可选,后置) - 带种子数据环境的实机验收
Generated by Claude Code
- ✅
进度更新:剩余项全部进入 PR。
- ✅ 服务端强制(anti filter-oracle)— framework#2630(draft):
plugin-security新增字段级谓词守卫,对where/orderBy/groupBy/having/aggregations/windowFunctions引用不可读字段的调用方查询直接 403(在 RLS 注入之前、针对调用方 AST 评估,RLS 策略可继续引用隐藏字段);拒绝而非静默删谓词(删$and分支会拓宽结果、重开 oracle)。10 个单测,plugin-security 183/183。 - ✅ spec 互斥守卫(同 PR):
NavigationItemSchema拒绝filters与recordId/viewName共存(correct-by-construction,防残留字段劫持优先级);legacyrecordId+viewName容忍不变。 - ✅ 导航活动态 + 设计器深链 — objectui#2273(draft,连带实施 导航活动态与设计器选中是「硬猜」的:缺 resolveHref 的逆函数 + nav id 深链契约(filters 项永不高亮) #2272 与 AppNavInspector 编辑 off-spec 的 nav path/kind(运行期不生效)——应改为 type + 分类型目标选择器 #2245):
resolveActiveNavItem逆解析器(修复 filters 项永不高亮的缺口)、?sel=nav:<id>深链、AppNavInspector 四落点改造。
仍开放:富操作符语法(
filter[<field>][<op>]=,等需求)、带种子数据环境的实机验收、packages/verify的 filter-oracle 不变量(#2630 备注为 follow-up)。
Generated by Claude Code
- ✅ 服务端强制(anti filter-oracle)— framework#2630(draft):
全部合并完成。
- ✅ objectui#2273(
6f8ad3c)—resolveActiveNavItem逆解析器(修复/datafilters 项永不高亮)+?sel=nav:<id>设计器深链 + AppNavInspector 四落点改造(连带 导航活动态与设计器选中是「硬猜」的:缺 resolveHref 的逆函数 + nav id 深链契约(filters 项永不高亮) #2272、AppNavInspector 编辑 off-spec 的 nav path/kind(运行期不生效)——应改为 type + 分类型目标选择器 #2245) - ✅ framework#2630(
3962023)— spec 落点互斥守卫 +plugin-security字段级谓词守卫(anti filter-oracle)+ 权限文档 Layer 6 更新
本线程剩余开放项(非阻塞):
- 富操作符语法
filter[<field>][<op>]=(等真实需求) - 带种子数据环境的
/data实机验收 packages/verify的 filter-oracle 不变量(framework#2630 已备注为 follow-up)
Generated by Claude Code
- ✅ objectui#2273(
背景
来源:导航建模讨论(#2245、#2247 app-composition 指南的后续)。场景:需要通过 URL 传过滤条件展示对象数据,同时显示常见筛选条件栏,并复用完整列表能力(分组/过滤/排序/展示类型切换)——此场景不应锚定到任何已保存视图。但当前对象路由始终解析出一个「活动视图」,URL 条件只能叠在该视图自带的 filter 之上。
现状 / 根因
已有机制(可复用,不需重建):
filter[<field>]=<value>URL 参数:packages/app-shell/src/views/ObjectView.tsx:994-1007解析,:1222-1228与活动视图的 filter AND 合并后下发查询(仅支持等值)。uf_<field>URL 参数(ADR-0047,packages/app-shell/src/views/userFilterUrlState.ts):常见筛选栏(UserFilters)选择的 URL 镜像,可分享、刷新不丢。ObjectView.tsx:557-564合成一个 All Records 网格——本方案将其泛化为可寻址表面。问题:
ObjectNavItem无filters),只能为每个条件建一个视图。设计:三路由模型(纯增量)
/:objectName/:objectName/view/:viewId/:objectName/data±filter[...]/uf_*/data表面的行为filter[...]/uf_*条件直接叠加其上,渲染为筛选栏中可见、可删除的 chip。viewDraft+CreateViewDialog),保存后跳转/view/<新id>进入工作台。defaultUserFilters或按 filterable 字段自动生成(选型待评审)。data加入new/view/record保留段清单(packages/app-shell/src/console/AppContent.tsx路由表)。/data语义明确(权限内全量、无 chrome),可作为未来嵌入/iframe 表面复用。导航契约(spec 侧,framework 仓)
ObjectNavItem加可选filters(支持{current_user_id}等模板变量,先例recordId/params)。resolveHref(packages/layout/src/NavigationRenderer.tsx:377)新增分支:带filters的 object 项 →/:objectName/data?filter[...];不带filters的项行为完全不变。操作符扩展(可后置)
保留
filter[field]=value等值简写,补富语法(如filter[field][gte]=...)。安全模型(裸表面 ≠ 绕过授权)
原则:视图不是安全边界。
/data显示的是「行级权限过滤后你有权看到的全量」,不是「所有数据」。保密性由权限层承担(@object-ui/permissions三层模型:对象级 actions /rowPermissions.filter/fieldPermissions,见packages/permissions/src/evaluator.ts),视图 filter 只是展示配置。/data(与/view/:viewId)返回 403 视图,不渲染空列表。/data的默认列自动生成自对象定义,必须先经fieldPermissions过滤——精选视图故意省略的敏感列不得因列合成而暴露(本方案唯一新增的暴露面)。filter[salary][gte]=...通过结果集反推数值)。服务端丢弃不可读字段上的谓词;筛选栏同样不提供这些字段。ir.rule式);URL 条件渲染为可见、可删除的 chip。rowFilter与字段级限制由 framework 在 API 查询层注入;UI 权限包仅做同构体验裁剪,不作为防线。验收标准
/:objectName/data?filter[status]=open展示行级权限内全量数据经 URL 条件过滤的结果,不受任何已保存视图 filter 影响;筛选栏/分组/排序/展示类型切换可用。/data表面不显示已保存视图 Tab 栏;展示类型切换(grid → kanban)后filter[...]/uf_*状态保留。/data上的调整不产生对已保存视图的写回;「另存为新视图」保存后跳转对应/view/:viewId。/:objectName与/view/:viewId的现有行为(默认视图解析、Tab 栏、filter[...]叠加语义)不变,现有 E2E 全绿。ObjectNavItem.filters经resolveHref产出/data?filter[...],有单测;不带filters的项输出不变。/data?filter[...]不越过行级 filter;敏感字段不出现在合成列与筛选栏;对不可读字段的 URL 谓词被服务端忽略(有测试覆盖)。/data+ URL 条件,值得沉淀的切面才建具名视图」。关联
filters字段应纳入其分类型目标选择器设计)uf_*URL 持久化)、ADR-0053(视图级 userFilters/quickFilters)