fix(security/sharing/rest/app-showcase): #2926 复测缺陷 + #2909 两存储审计#2983
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…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
b0925c6 to
ccb14f0
Compare
Contributor
📓 Docs Drift CheckThis PR changes 6 package(s): 105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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.
重评 #2926(app-showcase 权限模型第三轮复测)后落地全部框架侧修复,并顺带完成 #2909(声明 metadata↔记录两存储审计)。objectui 侧的 ④⑧ 在配套 PR:objectstack-ai/objectui#(同名分支)。
#2926 修复
SYSTEM_ROW_PROVENANCE.sys_position词表对齐 A4(保留 legacy 双键防御);修正错误安全注释;更新 stale 单测0dbaee3mapDataError尊重域错误显式携带的 4xxstatus/code(置于特化分支之后,限 4xx 防 5xx 消息泄露)0041d1b$参数findData对未识别$前缀参数抛400 UNSUPPORTED_QUERY_PARAM(裸参数隐式过滤不变)feb1142evaluateRule,置于kernel:listening(所有kernel:ready处理器/种子加载完成之后),幂等、best-effort0ec4f28+8a48156kernel:listening幂等补 8 条绑定(everyone + 7 persona);规格 isDefault 文案改写为双轨事实f81c00cf81c00c#2909 两存储审计
sys_sharing_rule加 readonlymanaged_by/customized;defineRule seed-not-clobber(管理员active:false不被 boot 复活);beforeUpdate 盖章钩子;不加写护栏(保持一等编辑面)61c2ee8bootstrap-declared-positions.test.ts锁定"seed 只刷 label/description"e42b1easys_capabilityseed 停止 clobber admin 编辑过的scoped0fdd3c55c3021行为变化(评审重点)
mapDataError新增 4xx passthrough。已验证 CONCURRENT_UPDATE/DELETE_RESTRICTED 的 409 结构化 envelope 不受影响(passthrough 在特化分支之后);5xx 仍走消息清洗。$foo等 OData 参数从"静默错结果"变为显式 400。内部调用方(import-runner、rest-server)只用已识别别名,不受影响。system/configlegacy 键,规避 normalizer 覆盖不全的失保护窗口。验证
turbo build56/56。objectstack dev) —— 过程中发现并修复了两个kernel:ready时序缺陷(均已体现在上面的 commit):sys_position_permission_set= 8 行(everyone + 7 persona)。注:最初为 0——原始 seed 方案在种子期无法解析岗位名;继而改kernel:ready又与 security bootstrap 顺序死锁(kernel:ready处理器顺序 await,app 的 2 分钟轮询阻塞了创建岗位行的 bootstrap)。最终锚定kernel:listening才稳定生效。exec岗位并重启后,种子 red 项目 → 1 条sys_record_share(exec/read),证明 backfill 在有接收者时于 boot 期物化(此前为 0 是因 fresh dev 无 persona 用户,非缺陷)。?$foo=1→ 400 UNSUPPORTED_QUERY_PARAM;?$top=2正常返回 2 行。managed_by=package。🤖 Generated with Claude Code
Generated by Claude Code