Repository navigation
fix(plugin-auth)!: a user preference's owner is fixed when it is saved - #29685
Conversation
One shared owner-fixed rule (owner-fixed-rule.ts) now holds the check sys_personal_view carried inline, and sys_user_preference registers it too: a non-system update that sends a user_id other than the stored one is refused 403 PERMISSION_DENIED, for every caller but the system. Claude-Session: https://claude.ai/code/session_01ADHkmTNFJm8TGKvyoyKegL Co-authored-by: Claude <noreply@anthropic.com>
… REST The rule is judged on the hook for every non-system caller, and over the real showcase stack for a member, an organization administrator and a platform administrator, with the echo and own-row controls. The system-context census row covers both per-user objects on the shared check, and the changeset records the narrowing. Claude-Session: https://claude.ai/code/session_01ADHkmTNFJm8TGKvyoyKegL Co-authored-by: Claude <noreply@anthropic.com>
…le alone Claude-Session: https://claude.ai/code/session_01JQwxbWZsNmNDugrjfzMoJW Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 41 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2be4faf22c491ddabe4c8430a1b493aabf6aa216 && git checkout 2be4faf22c491ddabe4c8430a1b493aabf6aa216
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 06881565bdced6c949f05d59f0059a3ff7409924 424e92959b28d0338b269e7452de3c3fc994fad6 && git checkout -B drift-repro 06881565bdced6c949f05d59f0059a3ff7409924 && git merge --no-ff 424e92959b28d0338b269e7452de3c3fc994fad6
node scripts/docs-audit/affected-docs.mjs --json 06881565bdced6c949f05d59f0059a3ff7409924
|
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf Contract-review-claim: session_01JQwxbXKRzgnVvjzxaYxLUf · head 424e929 · 2026-11-03T01:27Z Face: |
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf Contract reviewServed-tier: Narrowing face on card #29630, a security card. Per the security-findings rule (triage 6590532575, #29111) this record states the rule the PR restores and the judgments of it, and nothing of mechanism, reproduction or path. Read: the card body and all six comments (triage, two claims, two dev reports, the hand-off), the PR body, its file list, the net diff against ① Derived judgmentsThe rule the PR restores. A
Check-runs on this head, as read: 49 runs; 31 ② Semver level
③ Boundary flagsDev deviation 1 (one commit beyond the hand-off, comment and changeset wording only): verified; Dev deviation 2 (PR template headings folded into the os-dev order): prose, not reviewed. Dev deviation 3 ( Dev deviation in the hand-off (the delta's engine-rig shape not used for the unit pins): accepted; the fake-engine harness is the right instrument for a context-only hook, and the real-stack face is the REST file. Open question, step 3 of the card (the class: close per object here, or one platform-level card for the update floor): escalated to the seat, with this reading of Open question in the hand-off (closing keyword): the same answer. Escalated: the step-3 ruling, above. Nothing else. ④ Design judgmentThe narrowing, read against the published spec on I tried to break the rule four ways, each read in the cited code on Must-fix: none. Should-fix
None of the three changes the accept set or is a reason to hold the PR. Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf 维护者速读
席位核对ACCEPT ·
Generated by Claude Code |
Requested by Warren · project thread
Fixes #29630
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill or doc teaches changing a saved preference's owner; the one page that names the rule,
content/docs/permissions/system-context.mdxrow 26c, is updated here维护者速读
sys_user_preference加一条保存规则:更新时若把user_id改成另一个用户,一律 403PERMISSION_DENIED(系统写除外),与 feat(platform-objects): sys_personal_view, a reader's own named list views, with the owner's delete door (server step) #29575 给sys_personal_view的规则同形;两个对象现在共用 plugin-auth 里的一份检查owner-fixed-rule.ts。value的写入、回显原属主的写入、系统写入都不受影响。回滚即 revert 本 PR。packages/plugins/plugin-auth/src/owner-fixed-rule.ts(共用检查)、user-preference-hooks.ts(注册与对象判定)、personal-view-hooks.ts(改为调用共用检查,文案不变)。pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/user-preference-owner-fixed.dogfood.test.ts,预期 7/7 通过,三种调用者各一条 403。What changes
sys_user_preference. A non-system update that sends auser_idother than the stored one is refused403 PERMISSION_DENIED, for a member, an organization administrator and a platform administrator alike. The refusal names the row and theuser_idcolumn and echoes no other id. An update that echoes the storeduser_id, or does not send it, saves as before; a system write is not judged.packages/plugins/plugin-auth/src/owner-fixed-rule.ts(isOwnerChange,ownerFixedError,assertOwnerUnchanged) now holds the rule for bothsys_personal_viewandsys_user_preference.personal-view-hooks.tscalls it instead of its inline copy; its behaviour and message are byte-identical. Not exported from a published entry.user-preference-hooks.tsregisters onebeforeUpdatehook onsys_user_preference, inAuthPluginand inIdentityObjectsPlugin, beside the personal-view hooks, so the rule travels with the table.content/docs/permissions/system-context.mdxrow 26c covers both objects and anchors on the shared check (register regenerated)..changeset/29630-user-preference-owner-fixed.md:@objectstack/plugin-authpatch, BREAKING banner, both declaration lines, ADR-0087not-required.Resumed from
claude/project-thread-rq4y7m@34568e18(the previous card thread's hand-off), merged withorigin/main7dae3b3c. The last commit trims the shared check's header and the changeset's "Who sees it" line to the rule alone.Why
The card asks for the
sys_personal_viewshape (#29575) on this object: a user's preferences are their own UI state, and their owner is fixed when they are saved.Tests
On
424e9295:packages/plugins/plugin-auth/src/user-preference-hooks.test.ts: registration; a member, an org admin and a platform admin are refused 403; the message names the row and column and no other id; clearing the owner is a change of owner; controls: echo, value-only update, system write, another object.packages/qa/dogfood/test/user-preference-owner-fixed.dogfood.test.ts(REST): member, org admin and platform admin cannot hand their own preference to another user (403, the row keeps its owner); controls: echo saves; the owner reads, lists and updates their own preference.pnpm --filter @objectstack/plugin-auth build && test && typecheckunder the lock: VERDICT command-exit 0, Test Files 216 passed (216), Tests 3703 passed, 10 skipped.pnpm --filter @objectstack/dogfood test, the(narrowing)arm) under the lock on the full build: VERDICT command-exit 0, Test Files 400 passed, 3 skipped (403); Tests 3304 passed, 28 skipped (3332).3bbd5da8, each red then restored with the blob equal to HEAD andgit diff HEADempty): dropping the rule call reds the 5 unit refusal pins and keeps the 5 controls green; making the shared check always false reds 6 pins across both objects; guarding the registration (plugin-auth rebuilt, dist-preflight marker present, then--absentafter restore) reds the member and org-admin REST pins. The platform-admin REST pin stays green under that last ablation because an existing write check already refuses that caller's write there; the unit pin holds the rule on its own for that caller. Since then only comments and the changeset changed in tested files.Acceptance notes
Clause-②: no (narrowing): one accepted write is now refused, and no public export or published type moves.Verification
Derived gates on 424e929
pnpm build --concurrency=2): VERDICT command-exit 0, Tasks 117 successful, 117 total. History unshallowed for the teardown self-test fixture.node scripts/pm/dispatch-gates.mjs --commands(9 paths vs merge base7dae3b3c): 95 families. Each run once, stdin from /dev/null, exit captured before any pipe: 95 exit 0, includingcheck-plugin-teardown-shape.mjs --self-test(48 cases),spec check:skill-examples(282 prose examples),check:dual-build-cjs-loads(115 entry points across 70 packages),check:system-context-censusandcheck:type-check-debt(0 raw tsc errors above the ledger).dispatch-gates --ran: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lint: exit 0.Generated by Claude Code