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 无 code mapDataError 尊重域错误显式携带的 4xx status/code(置于特化分支之后,限 4xx 防 5xx 消息泄露) 0041d1b
REST list 静默吞掉未知 $ 参数 findData 对未识别 $ 前缀参数抛 400 UNSUPPORTED_QUERY_PARAM(裸参数隐式过滤不变) feb1142
启动期种子不物化共享规则 boot backfill 逐条 evaluateRule,置于 kernel:listening(所有 kernel:ready 处理器/种子加载完成之后),幂等、best-effort 0ec4f28 + 8a48156
fresh 部署 7 岗位零绑定静默降级 app-showcase 在 kernel:listening 幂等补 8 条绑定(everyone + 7 persona);规格 isDefault 文案改写为双轨事实 f81c00c
announcement 无种子 补 showcase_announcement 种子行 f81c00c

#2909 两存储审计

任务 内容 commit
P0/T1 sys_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
T3 sys_capability seed 停止 clobber admin 编辑过的 scope d0fdd3c
T4 ADR-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

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Canceled Canceled Jul 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-zhuang force-pushed the claude/app-showcase-permissions-defects-1h2ed4 branch from b0925c6 to ccb14f0 Compare July 15, 2026 15:52
@os-zhuang os-zhuang merged commit 28ba0c7 into main Jul 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-actions github-actions Bot 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants