Repository navigation
[finding] sys_member.add_member is offered to every organization member, owners and admins included, but its door admits only a platform admin #21886
Description
Ruled: 6019378035 · letter A-lite · 2026-10-06T15:16Z
Blocked-by: #22012
Filing-gate class ① (a defect with a named location, measured on a real boot). Filed by domain:spec seat 1 (session_01T9u38rswFp5Rw8DswRUReJ, seat post #6017) as the carrier the at-tier review of PR #21883 says is owed. Sources:
- the review record
5996636663on platform gap: a plain member is offered "Invite User" (and other org-admin affordances) that the server then refuses with 403 — an action's visibility cannot be gated on the membership grade #21795, ③ (b); - the platform gap: a plain member is offered "Invite User" (and other org-admin affordances) that the server then refuses with 403 — an action's visibility cannot be gated on the membership grade #21795 dev's report
5996276927, out-of-scope finding 2.
⛔ Not graded or routed here; ⛔ not a claim.
What a user sees
On an organization's member list, "Add Member" is offered to every member: plain members, owners and admins alike. Submitting it answers 403 unless the caller is a platform admin. This is the same courtesy defect #21795 fixes for the grade-gated affordances. add_member is outside #21795's reach table by that card's ruling A (5993018584: "one that targets no grade-gated endpoint takes none"), so PR #21883 leaves it as it is.
Where
packages/platform-objects/src/identity/sys-member.object.ts:144, theadd_memberaction. Its servedvisibleis theorganizationfeature gate alone. The platform gap: a plain member is offered "Invite User" (and other org-admin affordances) that the server then refuses with 403 — an action's visibility cannot be gated on the membership grade #21795 dev measured this on a real showcase boot, as served metadata for a plain member.packages/plugins/plugin-auth/src/organization-add-member.ts:35and:131: the door is platform-admin only ("including org owners/admins — they are NOT platform admins, ADR-0068"), with a 403 otherwise. This is cited from the source and its test; it was not re-measured.
Why it matters
It fails closed, because the server refuses. But it offers an org owner a button that never works for them, on the same surface #21795's dogfood test now proves clean for the grade-gated set. The QA clause that card answers ("the UI offers only what the server will accept") still fails for this one affordance.
Fix direction (not ruled)
Gate add_member's visibility on the same standing its door reads, platform admin, through whatever declared predicate the action visible scope already offers for that standing. If none is declared, the gap is a contract question, as #21795's was. A dogfood pin shows an org owner and a plain member see no "Add Member", while a platform admin does.
Prior art: #8092 (closed not_planned) was the same symptom on the workspace members page; #21795 (ruling A) re-opened the class for the organization surfaces.
Dedupe: mcp__github__search_issues (repo-scoped, semantic, closed included), add_member action offered to every organization member refused unless platform admin sys_member Add Member affordance → 16 hits, none this defect:
- platform gap: a plain member is offered "Invite User" (and other org-admin affordances) that the server then refuses with 403 — an action's visibility cannot be gated on the membership grade #21795: the grade-gated set;
add_memberis excluded there by its own ruling; - [finding]
sys_member"Add Member" action targetsPOST /organization/add-member, which better-auth 1.7.0-rc.2 never mounts (server-only) — on multi-org there is NO remaining UI path to attach an existing user to an org #9941 / docs: the newly mountedPOST /organization/add-memberis undocumented — and it is the ONLY multi-org path to attach an existing user #10050 / finding: three source comments claimteamIdonorganization/add-memberdefaults to the caller's active team — better-auth 1.7.1 has no such fallback #10532: the add-member endpoint's mount and docs; - console: workspace members page offers Invite / Remove member to the
memberrole; only the server 403 stops it #8092: the workspace page, closednot_planned; - console: org record page's default Members tab hides the email-invite entry — invite_user lives only on the Invitations tab #11544 / feat(auth,platform-objects): org join link V1 — sys_join_link + create/rotate/revoke/info/join, membership via governed addMember #11587: other members-tab and join-link cards.
Dedupe words: add_member platform admin affordance · Add Member offered org owner 403 · sys_member add_member visible
Activity
objectstack-fleet commentedon Oct 6, 2026
Ruling: batch #281 item 2 · letter A-lite · maintainer 「同意」 2026-10-06T15:15Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Batch #281 was presented in chat with the four options A–D from the dev's report (5999863859) and triage's 速读 (6000036525). The maintainer asked whether the platform-admin case needed that much, recalling the environment-variable configuration; the seat answered thatOS_PLATFORM_OWNER_EMAILalready settles who is a platform administrator and that the chain to the console already exists, so A was trimmed to A-lite; the maintainer answered 「同意」. Freshness gate: the body and all five comments (newest 6000036525) were re-read before this record; nothing newer exists. Thread-read: 6000036525.The ruling
A-lite. The standing predicate for platform-operator affordances is
current_user.isPlatformAdmin == true, as ADR-0068 D4 already rules and as the door (platform-admin-gate.ts:83), the session (auth-manager.ts:4055) and the console (objectuiexpressionUser.ts) already agree. What changes is the protocol text, not the mechanism: the stale "Deprecated, derived from positions" mark onEvalUserSchema.isPlatformAdminis lifted and the key is described as thePLATFORM_ADMINstanding of ADR-0095 D3, with a dated note under ADR-0068 D2 and D4. ⛔ Not taken: the rest of A (createEvalUsertaking the rung as input, the@objectstack/formulascope change), B (the'platform_admin' in current_user.positionsread the three codebases forbid), C (a new authorable key) and D (a capability gate that declares what the door never checks).Settled in the same stroke:
EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 (domain:spec,pm:queue, filed in this act): the.describe()and JSDoc, the regenerated spec docs, the two hand-written docs sentences, and the ADR-0068 note (Tier H, the maintainer's approval). This card keepsadd_member's gate (domain:engine), serial after spec(identity):EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012:visible: 'current_user.isPlatformAdmin == true'composed withrequiresFeature, aplatform-objectspin on the served predicate, and one dogfood pin that bindscurrent_userthe way the console does (throughextra, notuser:). identity: the platform-admin-gated actions onsys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903 (Blocked-by: #21886) then carries the thirteen siblings and the enumeration pin.State
pm:dispatched→pm:blockedin this act; body gainsBlocked-by: #22012and thisRuled:line. The unlock scan returns the card topm:queuewhen spec(identity):EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 closes; thedomain:engineseat re-claims it then.session_017ErfyP2Rx7XWHJA27QjyUi(thedomain:engineseat's claim 5999650144) · cause: the dispatch ended inneeds_decisionwith nothing pushed and the worktree removed (5999863859), and this ruling sequences the card behind spec(identity):EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 · destination:pm:blocked. The assignee is cleared in this act; the seat's next claim is a fresh one.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Triage: unlocked,
pm:blocked→pm:queue. #22012 landed the A-lite spec half, soadd_member's gate is next, as ruledBlocked-by: none
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T06:07Z. ⛔ Not a claim, ⛔ not a dispatch.The blocker is released (read at this write):
d5a14dd5c0, an ancestor oforigin/main.EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 closedcompletedat 2026-10-07T05:06Z.The premise holds on
main:EvalUserSchema.isPlatformAdmin(packages/spec/src/identity/eval-user.zod.ts:241) now describes the PLATFORM_ADMIN standing of ADR-0095 D3. The stale "Deprecated" mark is gone.sys_member.add_member(packages/platform-objects/src/identity/sys-member.object.ts:147) is still gated onrequiresFeature: 'organization'alone (:158).What this card does, by the ruling
6019378035(A-lite):add_member'svisiblereadscurrent_user.isPlatformAdmin == true, alongside the feature gate. A dogfood pin shows that an org owner and a plain member see no "Add Member", while a platform admin does. ⛔ The rest of A, and B, C and D, were not taken.The grade stands:
bug·priority:p3·domain:engine·area:identity. #21903, the family's closing card, staysBlocked-by: #21886.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 55 · 2026-10-07T06:32Z
Session:
session_017ErfyP2Rx7XWHJA27QjyUiAccount:
os-project-manager(the seat's linked user, asGET /useranswers it; always the card's assignee)Branch:
claude/issue-21886-add-member-visibilityWorktree:
objectstack-issue-21886Domain:
domain:engineSeat:
domain:engine#1Provenance:
EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 closed.pm:queue(6032081861): spec(identity):EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012 landed through PR fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018 (d5a14dd5c0), andEvalUserSchema.isPlatformAdminnow describes the PLATFORM_ADMIN standing of ADR-0095 D3.needs_decision(5999863859) with nothing pushed, and the ruling released it.File surface (at
origin/main56c88446ea), per the ruling 6019378035 and triage's unlock 6032081861:packages/platform-objects/src/identity/sys-member.object.ts:add_member(:147) gainsvisible: 'current_user.isPlatformAdmin == true', composed withrequiresFeature: 'organization'(:158).platform-objectspin on the served predicate (platform-objects.test.ts, whereadd_member's servedvisibleis pinned today).packages/qa/dogfood/test/,domain:cli's path, declared on [PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024): an org owner and a plain member see no "Add Member", and a platform admin does. It bindscurrent_userthe way the console does, throughextra, notuser:. Withuser:,@objectstack/formulare-derivesisPlatformAdminfrom positions (the earlier dev report's third observation)..changeset/21886-*.md(@objectstack/platform-objectspatch).createEvalUsertaking the rung as input, the@objectstack/formulascope change), and B, C and D. ⛔ Nopackages/spec,plugin-authorformulaedit. ⛔ identity: the platform-admin-gated actions onsys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903's thirteen siblings are not this card's.Container & model:
S,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate).Clause-②: no
patch. No key, export or parameter is added.Thread-read: 6032081861
Serial constraints cleared: at 2026-10-07T06:32Z:
filename: none touchessys-member.object.ts,platform-objects.test.tsor any file underpackages/qa/dogfood/test/.EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012, closedcompleted.metadata-protocolonly.sys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903 staysBlocked-by: #21886.objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{
"issue": 21886,
"status": "done",
"branch": "claude/issue-21886-add-member-visibility",
"pr": "#22064",
"session": "session_017ErfyP2Rx7XWHJA27QjyUi (subagent; the parent PM's session id, as the order states)",
"premise_still_valid": true,
"summary": "Implemented ruling A-lite as ordered.
sys_member.add_membernow declaresvisible: 'current_user.isPlatformAdmin == true'(sys-member.object.ts), andrequiresFeature: 'organization'composes onto it at parse time. The served predicate is(current_user.isPlatformAdmin == true) && features.organization != false. Pin (a) is the platform-objects lowering-matrix row. Pin (b) is one dogfood case on a real showcase boot: it bindscurrent_userthroughextrafrom each principal's served session, the way the console does, and shows that the platform admin alone is offered Add Member. An org owner who is not a platform admin, an admin, a delegated admin and a plain member are not. On the same boot the door refuses the owner and the member (403 PERMISSION_DENIED, no row written) and admits the platform admin (200, row lands). The reverse verification on committed HEAD turned (a) and (b) red, and the restore was proven by blob equality and an emptygit diff HEAD. origin/main (5cfd866) was merged before the PR; draft PR #22064 is open with the assignee set. After this comment, the worktree's node_modules, the worktree itself and the read-only objectui fetch are removed; the branch stays on the remote.","hypotheses": {
"H1": "CONFIRMED. The two keys compose with AND, the authored term first:
lowerRequiresFeature(packages/spec/src/kernel/public-auth-features.ts:352-419) returns(existing) && gate(:415-418). The first-party precedent carrying both keys is sys_user.enable_two_factor (sys-user.object.ts:534-535), pinned as(...) && features.twoFactor == true. Nothing was invented.","H2": "CONFIRMED. The served
visibleis exactly(current_user.isPlatformAdmin == true) && features.organization != false. It was read from the parsed SysMember in the matrix test and from the served/meta/object/sys_memberon the real boot (the dogfood case asserts every principal is served that one predicate). EvalUserSchema.isPlatformAdmin (packages/spec/src/identity/eval-user.zod.ts:225-241) describes the ADR-0095 D3 PLATFORM_ADMIN standing; no Deprecated mark remains.","H3": "CONFIRMED, with one correction. The console binds
current_userthroughextra. At the current objectui pin a58626c: fieldRules.ts:284 passes the scope asextra; ExpressionProvider.tsx:190 builds one subject under current_user / user / ctx.user / os.user, withfeaturesbeside it; expressionUser.ts:176 forwardsisPlatformAdminfrom the session. The session value isgrants.posture === 'PLATFORM_ADMIN'(plugin-auth auth-manager.ts:4083, emitted at :4107). Underuser:, formula re-derives it from positions (stdlib.ts:417-430 through createEvalUser); underextra, the bag is merged verbatim (stdlib.ts:462). The correction: the file's existingownerprincipal is the harness's seeded dev admin, and its served session carriesisPlatformAdmin: true, measured on the boot. It is reused as the platform admin. OS_PLATFORM_OWNER_EMAIL is not what makes it one: it is unset in the environment, absent from the dogfood package and the showcase config, and set by the harness only under a walled posture (packages/verify/src/harness.ts:481-497). So the rung comes from the single posture's unscoped admin_full_access grant. No org owner who is not a platform admin existed, so the case signs one up and sets its membership toownerin system context (the file's own shape). It asserts that this owner carriesorg_ownerandisPlatformAdmin: false. The plain member, admin and delegated admin are reused. The existing cases still bind throughuser:, unchanged.","H4": "CONFIRMED at 56c8844, with no change. No server path evaluates an action
visible: rest, runtime, objectql, services, metadata, metadata-protocol, core and plugins have zero non-test hits. The eight ExpressionEngine/celEngine.evaluate call sites in package sources evaluate other things: formula fields and defaults (objectql engine.ts:2263, :6190), hook conditions (hook-wrappers.ts:309), approval conditions (approval-service.ts:1995), share-link gates (share-link-service.ts:308) and automation steps (service-automation engine.ts:11913, :12031). Nothing on the server bindscurrent_user.isPlatformAdminfor this predicate. @objectstack/formula was not touched."},
"clause_2": "no, measured. The diff adds one
visiblevalue to an existing action and touches no file of the door: plugin-auth's organization-add-member.ts, the auth-plugin.ts mount and platform-admin-gate.ts are all unchanged, so the accept set is unchanged. No packages/spec file is in the diff, so no key, export or parameter is added.check-adr-0087-registration --base origin/main: '1 non-breaking changeset(s) seen'.check-changeset-no-major: 'This diff introduces nomajorbump'. The changeset is a @objectstack/platform-objects patch whose body carriesClause-②: no, and the PR body opens withFixes #21886thenClause-②: no.","files_changed": [
"packages/platform-objects/src/identity/sys-member.object.ts (+11: the
visiblepredicate and its comment)","packages/platform-objects/src/platform-objects.test.ts (+4 / -1: the add_member row of the lowering matrix)",
"packages/qa/dogfood/test/org-admin-affordance-reach.dogfood.test.ts (+106: docblock, the second-owner principal, one case)",
".changeset/21886-add-member-platform-admin-visibility.md (+11, new: @objectstack/platform-objects patch)"
],
"line_budget": "n/a",
"tests": "All at final HEAD bb53601 unless marked otherwise. Every build and test ran through scripts/pm/os-verify-lock.sh, and every VERDICT was command-exit 0. Build:
turbo run build --filter='@objectstack/dogfood^...' --concurrency=2, 63/63 after the merge. (a)pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2: Test Files 62 passed (62), Tests 996 passed (996). Verbose matrix run:SysMember...#add_member lowers to the previous hand-written predicatepasses, and the parsed object carriesvisible.source: '(current_user.isPlatformAdmin == true) && features.organization != false'. platform-objectstypecheck: exit 0, 0 TS errors;check:test-typecheck: OK;--listFilesshows the test file in tsconfig.test.json (1 hit), not in the main program (0). (b)pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/org-admin-affordance-reach.dogfood.test.ts: Tests 14 passed (14), includingAdd Member is offered to a platform admin alone .... dogfoodtypecheck: exit 0;--listFilesincludes the file (1 hit). Reverse verification on committed HEAD 57b18bf: scripts/ablation-replace.mjs in WRAP mode deleted the linevisible: 'current_user.isPlatformAdmin == true',(anchor x1 to x0, blob 9beac68ce1c7 to b8792313cac4), with an outertrap restore EXIT INT TERMon the absolute path. Rebuild of @objectstack/platform-objects: exit 0.ablation-dist-preflight.mjs @objectstack/platform-objects 'visible: \"current_user.isPlatformAdmin == true\"' --absent --source-marker=...: 'marker absent from all 66 built files', exit 0 (before the mutation the same spelling was at dist/index.mjs:2219). (a) went RED, 1 failed / 126 passed: expected 'features.organization != false' to be '(current_user.isPlatformAdmin == true) && features.organization != false'. (b) went RED, 1 failed / 13 passed: the offered set received [platform admin, org owner not a platform admin, org admin, delegated admin, plain member] against the expected [platform admin]. Direction: red, as predicted. Restore: ablation-replace and the outer trap both proved blob 9beac68ce1c7ed2b7143505916818a0a2e39a456 equal to the HEAD blob, withgit diff HEADempty. Rebuild, then preflight in present mode: 'marker present in 6 built files' and 'working tree clean against HEAD'. (a) passed 127/127 and (b) passed 14/14 again.","gates": "At bb53601.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 67 commands, identical before and after the merge and identical to the order's list. All 67 ran and their last runs exited 0.--ran ran.list: '67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN'. check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3) on the dogfood-closure build. After a fullturbo run build(72/72, under the lock) it passed (106 require entry points / 66 packages); check:dts-closure, check:lean-entry-closure, check:published-files and check:sourcemap-no-sources-content were rerun on that build and passed. Artifact-roster block: all 53 printed commands ran and 50 exited 0. Three answered NOT WIRED / NOT MEASURED (exit 2) for want of PR context. Rerun with context, all three passed: check-partof-closing-keyword with PR_BODY; check-closing-target-claim with PR_NUMBER=22064 ('PR #22064 closes #21886, and each carries a Claim: whose Branch: line names claude/issue-21886-add-member-visibility'); check-single-claim-paths with PR_NUMBER=22064. Partial: check-sdui-manifest passed, but its objectui version comparison is NOT CHECKED (no objectui checkout). check:console-injection is NOT MEASURED (no console dist; only its self-test ran). Neither surface is touched. The four symbol-anchor sweeps (check:adr-symbol-anchors, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors, check:adr-anchors) all passed. Lint, as a proven narrowing: eslint --no-inline-config --format json over the 3 touched TS files gave 3 files linted, 0 errors, 0 warnings. All 3 are in eslint's population (--print-config resolves). Invariance: the resolved configs carry no parserOptions.project/projectService, and eslint.config.mjs:327-328 states type-aware linting is never enabled. Left to CI: type-check lanes, Test Core, Dogfood Regression Gate, Build Core, repo-wide pnpm lint. CI state at report time: not read (in_progress or not started).","deviations": [
"objectui source (the pin a58626c) was read from a read-only shallow fetch (--depth 1, blob:none) into the session scratchpad. The container has no ../objectui checkout, and the GitHub API answered 403 for objectui in this session. It is deleted in cleanup.",
"Pin (b) adds one principal (a second org owner, signed up and set to
ownerin system context) to the file's beforeAll, and its case writes a second organization and one membership. No existing principal was an org owner who is not a platform admin. The case runs last in the file, so no earlier case reads that state. The existing cases' binding is unchanged.","There are two code commits, not one. The second (57b18bf, test-only) restates the case's verdict loop as one named-set equality, so the reverse verification shows every principal's cell at once instead of stopping at the first.",
"The harness's attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md's model-free trailer pair and the session-URL PR footer were used instead (AGENTS.md takes precedence; the reminder itself defers to it).",
"git push ran four times: the empty-branch probe, the two commits, and the merge of origin/main. The budget names
git pushas one category, and none was a force-push.","The PR carries the labels
documentation,size/m,testsandtooling, which another actor (the path labelers) set. They were left untouched.","No write or command was refused by the session's permission checks."
],
"mcp_calls": "0",
"api_writes": "3 relay strokes, each
POST /repos/objectstack-ai/objectstack/dispatchesfrom the session and executed as objectstack-fleet[bot]: (1) pr_create, run 37587839117, becomingPOST /repos/objectstack-ai/objectstack/pulls(draft) and creating #22064 (read-back: 11798 bytes sent and stored, identical); (2) label-write --assign os-project-manager, run 37587907840, becomingPOST /repos/objectstack-ai/objectstack/issues/22064/assignees(read-back matches); (3) this os-dev-report comment through scripts/pm/post-stamped.mjs, becomingPOST /repos/objectstack-ai/objectstack/issues/21886/comments. Plus git pushes (not REST). Reads: single-card REST reads of #21886 and its comments, one REST read of PR #22064, and the read-only GitHub calls of check-closing-target-claim and check-single-claim-paths.","open_questions": [],
"out_of_scope_findings": [
"observation (not class a/b/c; ruled predicate): the door also admits the legacy better-auth
user.role === 'admin'scalar (plugin-auth platform-admin-gate.ts:80-84), whichcurrent_user.isPlatformAdmin == truedoes not read. A pre-ADR-0068-D2 deployment whose user carries the scalar but not the rung would be admitted by the door and not offered the button. reach: none measured; the gate header says nothing in ObjectStack writes the scalar and that re-synthesizing it is vetoed. carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed","coverage note (not a finding): the dogfood case exercises the single-posture route to PLATFORM_ADMIN (the seeded admin's unscoped grant), not the walled postures' OS_PLATFORM_OWNER_EMAIL route. carrier: #21903 (the family close-out,
Blocked-by: #21886), if it wants a walled-posture principal · noted, not filed"]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Landed: PR #22064 →
77a94d8cedonmain. It merged through the merge queue at 2026-10-07T08:35Z, after entering the queue at 2026-10-07T08:08Z. Verified at 2026-10-07T08:36Z.domain:engine#1·session_017ErfyP2Rx7XWHJA27QjyUi.origin/mainas a single-parent commit (parent5cfd8661c4). Its diffstat is the reviewed one: 4 files, +132/-1.main(the maintainer's ruling A-lite, 6019378035):sys_member.add_memberdeclaresvisible: 'current_user.isPlatformAdmin == true'. The served predicate is(current_user.isPlatformAdmin == true) && features.organization != false.platform-objectslowering matrix pins it.current_useras the console does (throughextra). The platform admin alone is offered "Add Member"; an org owner, an org admin, a delegated admin and a plain member are not. The door refuses the owner and the member with 403 and admits the platform admin.Fixes #21886closed this card ascompleted.pm:dispatchedis removed in this act. No other card was closed by the body.@objectstack/platform-objectspatch,Clause-②: no): on an organization's member list, "Add Member" is offered only to a platform administrator, the one standing its endpoint admits. Owners and admins no longer see a button that answers 403.sys_user,sys_oauth_applicationandsys_sso_providershow no standing term invisible— the affordance family's closing card, with an enumeration pin (after #21886's ruling) #21903 (the thirteen sibling actions and the enumeration pin) wasBlocked-by: #21886, and that blocker is now released. It goes to triage's unlock scan; it is not claimed here.Generated by Claude Code
fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN…
fix(platform-objects): offer Add Member only to a platform admin (obj…