Repository navigation
feat(plugin-security,plugin-auth,verify,platform-objects)!: a permission-set grant names its set by name — no writer fills permission_set_id (#15204 stage 6b-2-pre) - #22863
Conversation
… their permission set — no writer fills permission_set_id Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…min reconcile, name backfill) Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…ngine in tests; the reconcile's duplicate check keeps its legacy reach Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…s its set with zero sys_permission_set rows Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…r name the set Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…m-context census row follows the by-name refusal Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…id; no new query-option casts Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…no duplicate keys Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…by name Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
…b2pre-grant-by-name
📓 Docs Drift CheckThis PR changes 5 package(s): 50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 13 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 147 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 d91ee7c331befa42f62f72575ffd883ca979688e && git checkout d91ee7c331befa42f62f72575ffd883ca979688e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 55382dc02a2168585a024ba142b98d8bce9910e6 d453ea50d7f894e4bf451b51778df0a952f8500c && git checkout -B drift-repro 55382dc02a2168585a024ba142b98d8bce9910e6 && git merge --no-ff d453ea50d7f894e4bf451b51778df0a952f8500c
node scripts/docs-audit/affected-docs.mjs --json 55382dc02a2168585a024ba142b98d8bce9910e6
|
Contract reviewServed-tier: PR #22863 (stage 6b-2-pre of #15204, branch The stage's three sentences (6109520247, Q1 → A), each against the diff:
① Derived judgmentsEvery accept-set and public-surface change the diff implies, named right or wrong:
ADR-0131 reading: this is D10's first step as the seat named it — every writer references the set by name (D4); the write door and the writers consult the registry-backed catalog and nothing else (D4, "resolution reads the registry"); nothing is deleted or constrained before the rewrite is verified (D10 fate 2's gate). None of §8's inherited rules is touched: no driver arm, no default owner, no deletion, no merged two-source list, no edit under ② Semver level
③ Boundary flagsDev report 6110797712,
PR body acceptance notes: uniqueness (= Q4). Pre-ADR-0113 databases with a
Not in this stage (PR body): One pin removed and not replaced: an unrelated update to a legacy unnamed grant (name Gates on this head, read at render time. Completed and green: Auto Label, Build Core, Build Docs, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate (1/3, 2/3, 3/3), Dogfood Verify CLI, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, Temporal Conformance (live PG + MySQL), Test Core (6/6), The card this PR closes must claim this branch, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Type Check · workspace, filter. Skipped by design: Console Pin Gate, Packed-tarball smoke (opt-in). Still pending at render time: Test Core (1/6), (2/6), (3/6), (4/6), (5/6). Their conclusions are the gate verdicts; the queue holds the landing on them and this record does not stand in for them. Nothing on the head is red. Implemented-by: VERDICT: PASS Generated by Claude Code |
…ce example no longer name permission_set_id Claude-Session: https://claude.ai/code/session_01MZuVPXoYeF1Pa3G4ERmTPz Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Delta round on PR #22863 (stage 6b-2-pre of #15204, branch (a) The delta is one text-only commit. (b) The delta's own ①②③. ① The TSDoc on ② The error-catalog example — matches the engine's real refusal at this head. ③ The spec changeset level — (c) The earlier record on this head. Nothing in 6110902443's ①–③ changes: the compare shows the delta touches no source, test, object, page, skill or changeset that record judged, so every hunk it read is byte-identical here. Three of its lines move: its ① row 15 gains the two texts above; its ③ ① Derived judgmentsAdopted by citation from 6110902443, rows 1–16 and its ADR-0131 reading, each re-read against the net diff at this head: The delta adds:
Seat answer 6109520247's three sentences stand as the earlier record found them: 1 and 2 met on the diff; 3 ("the Setup grant picker lists the security catalog") not on this head, moved by 6110777865 to E2 and since measured (6111111092: on a fresh ② Semver level
③ Boundary flags
Gates on this head, read at 2026-10-11T16:37Z. Completed and green: Auto Label, Build Core, Build Docs, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate (aggregate, 1/3, 2/3, 3/3), Dogfood Verify CLI, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, Temporal Conformance (live PG + MySQL), Test Core (1/6), (2/6), (6/6), The card this PR closes must claim this branch, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Type Check · workspace, TypeScript Type Check, filter. Skipped by design: Console Pin Gate, Packed-tarball smoke (opt-in). Still pending at render time: Test Core (3/6), (4/6), (5/6). Their conclusions are the gate verdicts; the queue holds the landing on them and this record does not stand in for them. Nothing on the head is red. Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)· #15204 阶段 6b-2-pre:授权按名称记录席位 改了什么
为什么改
风险与代价(含回滚)
席位意见
你要做的 Generated by Claude Code |
Part of #15204
Clause-②: yes (narrowing)
Stage 6b-2-pre, grant by name (ADR-0131 D10's first step: every grant names its set, then the id column goes). This PR leaves #15204 open: stage 6b-2 (the seeders), stage 8 / C7b (the
permission_set_idcolumn and its data) and the rest of the epic remain.What changes
sys_user_permission_set.permission_setis the grant's one reference to its permission set, by machine name.permission_set_idis optional; no writer fills it, and no write derives anything from it.objects/sys-user-permission-set.object.tspermission_set_idrequired lookup;permission_setread-only, derivedpermission_setrequired and writable;permission_set_idoptional ("legacy id"); title reads the namegrant-permission-set-name.ts400 VALIDATION_FAILED,reference_not_foundatpermission_set(sibling ofposition-catalog-refusal.ts); a missing name is the engine's400 VALIDATION_FAILED/requiredbootstrap-platform-admin.ts(promotion)admin_full_accessrow id{ permission_set: 'admin_full_access' };findExistingPlatformAdminreads by name onlyauto-org-admin-grant.tsplugin-auth/auth-manager.ts(self-registration)sys_permission_setrows for the idplugin-auth/last-admin-guard.tspermission_set_idleftGRANT_STANDING_KEYSdelegated-admin-gate.tscleanup-package-permissions.ts(uninstall)verify/src/rls.tsplatform-objects/pages/sys-user.page.tslinkField: permission_set_id)valueField: 'name',linkField: 'permission_set'); list shows the nameThe property 6b-2 needs, pinned: no grant path needs a
sys_permission_setrow. Fresh-database pins with zero rows: the organization-admin reconcile under a wall and undersingle(grant-permission-set-name.writers.test.ts,auto-org-admin-grant.test.ts), the data door (grant-permission-set-name.test.ts), self-registration (plugin-auth/audience-posture.test.ts), the verify persona (verify/rls-persona-grant.test.ts). The first-platform-admin promotion still runs insidebootstrapPlatformAdmin, which seeds rows until 6b-2; its grant is pinned name-only and the holder read is pinned with every row deleted afterwards.Restrict-only legacy reach (kept, deliberately). A grant written before the name column carries only its id until the existing one-time backfill names it (at
kernel:bootstrapped). These reads reach such a grant through its id only to restrict: the platform-admin bootstrap's withheld promotion (leg C, now only when a legacy row exists), the org-admin revoke / superseded-variant sweep / duplicate check, and the uninstall sweep. Nothing confers through the id. They go with the column (C7b). The backfill itself and the D10 reference report are unchanged.Not in this stage (measured)
sys_position_permission_set.permission_set_id(the junction lookup) stays: since stage 1 it grants nothing; its writers left on this tree are Setup's position page, the app-crm / app-showcasebind-position-sets.ts, U2's suggestion accept, and the anchor-binding gate insecurity-plugin.ts. It goes with the junction object in stage 8.packages/spec/src/ui/component.zod.ts(~:1597): the TSDoc's "Assigned Users" example namedrelationshipField=permission_set_id, which this PR's head refuses. The patch round (the seat's review 6110944288, commitd453ea50d7) rewrote it by name (relationshipField=permission_set,relationshipValueField=name) with a@objectstack/specpatch changeset. It is text only, and no generated artifact moved. The same round movedcontent/docs/api/error-catalog.mdx's dangling-reference example tosys_user_permission_set.user_id→sys_user. (Updated by the seat.)suggested-audience-bindings.tsuntouched (U2's; feat(plugin-security)!: a package's isDefault suggestion takes effect once a manage_metadata holder accepts it for the deployment (#15204 stage U2) #22854 landed and is merged into this branch).Setup's grant picker and objectui
The picker now writes the name with objectstack metadata alone. It still lists
sys_permission_setrows: objectui'sRelatedListAdd picker (pinned0df67f237c5a,packages/plugin-detail/src/RelatedList.tsx) reads object records only (add.picker.objectthrough the data source); C9's registry reads live in the metadata-admin pages, not in this picker. Listing the security catalog needs an objectui change; reported to the seat for objectui#7611.Size, tiers and review
skills/objectstack-data/rules/security.mdis edited — it prescribed writing the record id intopermission_set_id, which this PR refuses. Readings: that file 216 → 213 lines; theskills/objectstack-datapackage (all.md) 3,721 → 3,718.Clause-②: yes (narrowing)); the PM spawns it.@objectstack/plugin-securityminor (BREAKING, ADR-0087not-required (no-migration-prescription)),@objectstack/plugin-auth,@objectstack/verify,@objectstack/platform-objectspatch, and@objectstack/specpatch (TSDoc text only).Verification (local)
plugin-security and the dogfood files below were re-run on the merged tree (
origin/main55382dc02, U2 included); the other suites ran on this branch before that merge, which brought no change to plugin-auth, verify, platform-objects, plugin-hono-server, plugin-sharing or organizations source (it did touch rest and cli; CI re-runs them).@objectstack/dogfood(includes all four packages) after theorigin/mainmerge.@objectstack/plugin-securityfull suite on the merged tree: 196 files, 3,973 passed.@objectstack/plugin-auth: 136 files, 2,774 passed.@objectstack/verify: 31 files.@objectstack/platform-objects: 69 files, 1,083 passed.@objectstack/rest: 277 files, 5,454 passed.plugin-hono-server28,plugin-sharing41,organizations12 files green.@objectstack/cli: unit tier 281 files, 4,185 passed; integration tierpackage-install-local-uninstall-cleanups16 passed (it grants the package set by name and proves the uninstall revokes it).suggested-binding-acceptanceafter the merge. (Count corrected by the seat; the dev's report flagged it.)dispatch-gates --ran: 106 derived families accounted for, 106 run; every one green or re-run green after a fix (check-system-context-censusrow 23c rewritten;check:query-options-erasureback at 236 after removing three test casts). Three first runs were prerequisite-blocked (shallow clone, unbuilt dists) and re-measured green after fetching the fixture commit and building.Acceptance notes
(user_id, permission_set_id, organization_id). A by-name grant stores NULL there, which SQL keys apart, so two by-name grants of one set to one user in one organization are not refused by the database; every platform writer checks existence by name first. A unique key on the name belongs with the column drop (C7b), which can dedupe the data.NOT NULLbefore protocol 17 forpermission_set_idwould refuse a name-only insert at the driver. ADR-0113's ruling records such databases as not measured to exist; the drift report already offersrelax_not_nullfor it.permission_set_idis not refused; it is stored and read by nothing that grants. Refusing it is left to the column's retirement rather than built here.zh-CN,ja-JP,es-ES) for the two columns were rewritten so they no longer describe the retired derivation.维护者速读(草稿)
改了什么:用户↔权限集的直接授权(
sys_user_permission_set)改为只按权限集的名称记录。所有写授权的地方(首个平台管理员、组织管理员自动授权、自助注册、verify 探针、Setup 用户页的「授予权限集」)都只写名称,不再读sys_permission_set行去取 id;permission_set_id变为可选,不再被写入或用于推导任何东西。非系统写入若名称不在安全目录中,返回 400VALIDATION_FAILED(reference_not_found)。为什么改:ADR-0131 D10 规定先让每条授权按名称引用权限集,再删除 id 列。下一阶段 6b-2 要删除写这些行的种子程序;若授权仍依赖这些行,新库将没有首个平台管理员、墙内没有组织管理员授权、Setup 授权选择器为空。本 PR 让授权路径不再需要这些行。
风险与代价(含回滚):外部调用方若仍只写
permission_set_id会被 400 拒绝(需改写为permission_set: '集名'),changeset 已写明。旧数据的授权仍由已有的一次性回填命名,旧 id 仅用于撤销与去重。数据库层面对按名称的重复授权没有唯一约束,由写入方先查后写。回滚:直接 revert 本 PR,无数据迁移、无列变更。另:改了对外发布的技能文件skills/objectstack-data/rules/security.md(原文指导写 id,现已不成立),因此本 PR 为 Tier H。席位意见:
你要做的:契约复核(PM 安排)通过后,批准合并(Tier H)。另需决定 Setup 授权选择器何时改为直接列出安全目录(需 objectui 改动)。
Generated by Claude Code