Skip to content

fix(security/sharing/rest/app-showcase): #2926 复测缺陷 + #2909 两存储审计 - #2983

Merged
os-zhuang merged 10 commits into
mainfrom
claude/app-showcase-permissions-defects-1h2ed4
Jul 15, 2026
Merged

fix(security/sharing/rest/app-showcase): #2926 复测缺陷 + #2909 两存储审计#2983
os-zhuang merged 10 commits into
mainfrom
claude/app-showcase-permissions-defects-1h2ed4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

重评 #2926(app-showcase 权限模型第三轮复测)后落地全部框架侧修复,并顺带完成 #2909(声明 metadata↔记录两存储审计)。objectui 侧的 ④⑧ 在配套 PR:objectstack-ai/objectui#(同名分支)。

#2926 修复

#问题修复commit
① 🔴锚点岗位可被物理删除(#2930 护栏被 #2934 A4 词表回归打穿)SYSTEM_ROW_PROVENANCE.sys_position 词表对齐 A4(保留 legacy 双键防御);修正错误安全注释;更新 stale 单测0dbaee3
记录范围拒绝退化成 400 无 codemapDataError 尊重域错误显式携带的 4xx status/code(置于特化分支之后,限 4xx 防 5xx 消息泄露)0041d1b
REST list 静默吞掉未知 $ 参数findData 对未识别 $ 前缀参数抛 400 UNSUPPORTED_QUERY_PARAM(裸参数隐式过滤不变)feb1142
启动期种子不物化共享规则boot backfill 逐条 evaluateRule,置于 kernel:listening(所有 kernel:ready 处理器/种子加载完成之后),幂等、best-effort0ec4f28 + 8a48156
fresh 部署 7 岗位零绑定静默降级app-showcase 在 kernel:listening 幂等补 8 条绑定(everyone + 7 persona);规格 isDefault 文案改写为双轨事实f81c00c
announcement 无种子补 showcase_announcement 种子行f81c00c

#2909 两存储审计

任务内容commit
P0/T1sys_sharing_rule 加 readonly managed_by/customized;defineRule seed-not-clobber(管理员 active:false 不被 boot 复活);beforeUpdate 盖章钩子;不加写护栏(保持一等编辑面)61c2ee8
T2新建 bootstrap-declared-positions.test.ts 锁定"seed 只刷 label/description"e42b1ea
T3sys_capability seed 停止 clobber admin 编辑过的 scoped0fdd3c
T4ADR-0094 addendum 三行 "Audit that…" 改写为 resolved 结论 + 取舍记录55c3021

行为变化(评审重点)

  • F2:mapDataError 新增 4xx passthrough。已验证 CONCURRENT_UPDATE/DELETE_RESTRICTED 的 409 结构化 envelope 不受影响(passthrough 在特化分支之后);5xx 仍走消息清洗。
  • F3:未识别 $foo 等 OData 参数从"静默错结果"变为显式 400。内部调用方(import-runner、rest-server)只用已识别别名,不受影响。
  • F1:词表保留 system/config legacy 键,规避 normalizer 覆盖不全的失保护窗口。

验证

  • 单测:plugin-security(433)、plugin-sharing(93)、rest(273)、metadata-protocol、spec 全绿;turbo build 56/56。
  • E2E dogfood(fresh sqlite,objectstack dev) —— 过程中发现并修复了两个 kernel:ready 时序缺陷(均已体现在上面的 commit):
    • ① DELETE 内置 everyone 岗位(先删其 FK 绑定)→ 403 业务消息"provided by the platform — cannot be deleted"。
    • ② fresh boot 后 sys_position_permission_set = 8 行(everyone + 7 persona)。:最初为 0——原始 seed 方案在种子期无法解析岗位名;继而改 kernel:ready 又与 security bootstrap 顺序死锁(kernel:ready 处理器顺序 await,app 的 2 分钟轮询阻塞了创建岗位行的 bootstrap)。最终锚定 kernel:listening 才稳定生效。
    • ③ 将 admin 赋 exec 岗位并重启后,种子 red 项目 → 1 条 sys_record_share(exec/read),证明 backfill 在有接收者时于 boot 期物化(此前为 0 是因 fresh dev 无 persona 用户,非缺陷)。
    • ?$foo=1400 UNSUPPORTED_QUERY_PARAM;?$top=2 正常返回 2 行。
    • F5:3 条 sys_sharing_rule 均 managed_by=package
  • 依赖单测(rest.test.ts FORBIDDEN→403 用例);E2E 需受限 persona 用户,未单独搭建。

🤖 Generated with Claude Code


Generated by Claude Code

@vercel

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specCanceledCanceledJul 15, 2026 4:58pm

Request Review

claude added 10 commits July 15, 2026 15:43
…ows (#2926 ①)
The system-row write gate (#2918/#2930) keyed sys_position provenance on the
legacy managed_by values (system/config), but the A4 vocab unification (#2934)
stamps and boot-normalizes rows to platform/package — so the gate silently
stopped firing for positions, letting admins physically delete the everyone/
guest audience anchors once their bindings were removed. Guard both the
canonical and legacy vocabularies, update the gate tests to the canonical
values, add legacy-row regressions, and correct the stale 'no runtime path
branches on legacy values' safety comments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
… ⑦)
Record-scope authorization denials (plugin-sharing throws status=403,
code=FORBIDDEN) degraded to a bare 400 with no code because the generic data
routes call mapDataError directly, bypassing sendError's status passthrough.
Add a guarded 4xx passthrough after the structured-code branches (409
envelopes keep their rich fields; 5xx still goes through the sanitizing
heuristics; oversized messages fall back to generic text), and stop logging
expected statuses as unhandled on the list route.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…400 (#2926 ⑩)
Unsupported $ parameters (e.g. $foo, $inlinecount) used to fall into the
implicit-filter bucket and silently match zero rows — and before the $filter
alias existed, were dropped entirely, returning the unfiltered first page to
callers that believed they had a filtered result set. All supported aliases
are consumed before the guard, so anything $-prefixed that remains is a hard
400 UNSUPPORTED_QUERY_PARAM naming the offending keys and the supported list.
Bare-key implicit equality filters are unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
Sharing-rule grants are materialized by write hooks that deliberately skip
isSystem writes, so seed-loader records never got sys_record_share rows —
demo data with matching rules was broken on a fresh deploy until each record
was touched at runtime. Reconcile every active rule once per boot (idempotent,
best-effort per rule) right after the rule hooks bind.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…2909 P0/T1)
sys_sharing_rule is record-authoritative (ADR-0094 addendum): declared rules
are a boot seed, the row is the authority — but every boot re-ran a clobbering
upsert, so an admin's active:false on an over-sharing rule was silently
resurrected on redeploy. Add readonly managed_by (A4 tri-state) + customized
provenance columns, put defineRule in seed mode when the bootstrap passes
managedBy:'package' (pristine/legacy rows adopted and updated; admin-authored
or customized rows untouched), and stamp customized via a beforeUpdate hook on
any non-system edit of a seeded rule. No write gate on purpose — sharing rules
stay a first-class admin authoring surface; edits are remembered, not blocked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…cs (#2909 T2)
sys_position is record-authoritative — the declared seeder refreshes only
label/description and must never touch bindings, active, is_default,
delegatable, or managed_by. That behavior predates these tests but was never
locked; regressions would silently clobber admin state at every boot.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…2909 T3)
scope is an admin-editable classification select on sys_capability, but the
curated seeder refreshed it every boot — silently reverting admin
reclassifications. Make it seed-once (insert only); label/description remain
platform-owned and keep refreshing. Lock both sides with tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…d-not-clobber tradeoff (#2909 T4)
The three 'Audit that…' per-type rows are now shipped decisions:
sys_sharing_rule = record-authoritative with provenance + seed-not-clobber;
sys_position = seed-only identity/display, locked by test; sys_capability =
seed-not-clobber with scope seed-once. Record why sharing rules got no write
gate (admin tuning surface), the customized-stamp boundaries, and the scope
migration cost.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…uncements; document isDefault dual-track (#2926 ②⑤)
A fresh deploy booted with ZERO position↔permission-set bindings — every
persona silently degraded to the everyone baseline. Bindings are
record-authoritative (ADR-0090/0094) and cannot be a declarative seed (the
seed loader runs before the security bootstrap creates sys_position /
sys_permission_set, so name references can't resolve). They are ensured
imperatively by registerShowcasePositionBindings on kernel:listening — the
phase that fires only after every kernel:ready handler (incl. the security
bootstrap) has settled, so the referenced rows exist. everyone→member_default
is bound here too: the framework only auto-binds an app's isDefault set to
everyone when it is application-owned; the showcase ships as a package, so its
default lands in sys_audience_binding_suggestion (pending admin confirmation)
and is not live until confirmed.
Also seeds showcase_announcement demo rows and documents the isDefault
dual-track (app-level auto-bind vs package-level suggestion) in the spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
…rows materialize (#2926 ③)
The boot backfill ran inside a kernel:ready handler, but SeedLoader also
seeds on kernel:ready (raced against a budget, in a different AppPlugin
handler). Since kernel:ready handlers fire sequentially in registration
order, the backfill could run before the seed records exist and materialize
nothing. Move the reconcile to kernel:listening (Phase 4), which the kernel
fires only after every kernel:ready handler has settled — so the seeded rows
are present. Verified end-to-end: a seeded red project now yields a
sys_record_share to the exec recipient at boot, no runtime touch required.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
@os-zhuang
os-zhuangforce-pushed the claude/app-showcase-permissions-defects-1h2ed4 branch from b0925c6 to ccb14f0CompareJuly 15, 2026 15:52
@os-zhuang
os-zhuang merged commit 28ba0c7 into mainJul 15, 2026
1 of 2 checks passed
@os-zhuang
os-zhuang deleted the claude/app-showcase-permissions-defects-1h2ed4 branch July 15, 2026 15:52
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/xl labels Jul 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/plugin-security, @objectstack/plugin-sharing, @objectstack/rest, @objectstack/spec.

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

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx(via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/vercel.mdx(via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/objectql)
  • content/docs/permissions/access-recipes.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx(via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx(via packages/plugins/plugin-security, packages/plugins/plugin-sharing, packages/rest, @objectstack/spec)
  • content/docs/permissions/explain.mdx(via @objectstack/plugin-security)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via packages/plugins/plugin-security, packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/objectql, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/objectql, @objectstack/plugin-security, @objectstack/plugin-sharing, @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/objectql)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/plugins/plugin-sharing, packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/objectql, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/ui/audience-based-interfaces.mdx(via packages/plugins/plugin-security)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)

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/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude