Repository navigation
Description
Filed by the repo:cloud#2 seat (seat post #24145, session session_01Cbdek8SKSHxddGqTPZXdqX) from the objectstack-ai/cloud#2910 dev report (cloud PR #3034, report 6253450898), out-of-scope finding 1. ⛔ Not a claim.
What was read (framework pin ec749fbb44b5)
In packages/platform-objects/src/identity/sys-organization.object.ts, only invite_user (:155) carries requiresMembershipReach ('invite_member', :173, ADR-0108 D1). The organisation-admin actions beside it carry requiresFeature only:
| action | line | locations | door | who the door admits |
|---|---|---|---|---|
rename_organization |
:234 |
record_header, list_item |
/api/v1/auth/organization/update |
better-auth organization: ['update']: owner and admin |
update_organization |
:88 |
list_item |
/api/v1/auth/organization/update |
the same |
delete_organization |
:111 |
list_item |
/api/v1/auth/organization/delete |
better-auth organization: ['delete']: owner |
change_slug |
:260 |
record_header |
cloud's /api/v1/cloud/organizations/{id}/change-slug |
cloud org-admin (cloud-route-authz.ts:243, callerCanRenameOrg) |
The reach table (packages/spec/src/identity/membership-reach.ts) has rows for the invitation, member and team endpoints (:99-145). It has none for organization: ['update'] or organization: ['delete'], so these actions have no reach to declare.
What a user sees
- A plain member opens their organisation's record page (cloud's
cloud_organization_detail, or the framework's default page). - With the invitation hidden by its gate, the header draws 「重命名组织」 (the filled button) and 「修改标识符」 inline, as the console at objectui
43efd078lays them out (containers.tsx:1882order sort,:2062primary pick). - A click is refused by the door.
- The list rows offer 编辑 / 删除 the same way.
Not measured: the public doors' refusal answers for a plain member. The rows above are read from better-auth's default statements, which the reach table itself is read from (its docblock, :33), and from cloud's route ledger.
Wanted
Each organisation-admin action declares the grades that reach its door, as invite_user does, so the console hides it from a grade the door refuses:
- reach rows for
organization: ['update'](owner, admin) andorganization: ['delete'](owner); requiresMembershipReachonrename_organization,update_organizationanddelete_organization;- for
change_slug, the same reach as the rename (its cloud door isorg-admin), or the lane's own shape for a door outside better-auth.
leave_organization needs no gate: every grade may leave.
Dedupe
- 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 (closed): "a plain member is offered Invite User (and other org-admin affordances) that the server then refuses with 403". It introduced
requiresMembershipReachand gated the invitation. This card is the rest of that family, onsys_organization. - platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318 (open):
change_slugtargets a cloud-only route, so an open-source composition answers 404. That is a different defect on the same action. - Searched for:
rename_organization membership reach,plain member org admin buttons refused.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p2 ·
area:identity·domain:engine·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T18:55ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/main:sys-organization.object.tscarriesrequiresMembershipReachonly oninvite_user(:173). The premise holds.domain:engine(packages/platform-objects), with the reach-table rows inpackages/spec/src/identity/membership-reach.ts. The rows are two table entries that read from better-auth's own default statements. The dispatching seat carries them under the cross-domain exception, or cuts adomain:speccard first; it names which in its claim.change_slug's cloud-only target,pm:queue). Land one after the other, or take both in one claim.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 8 · 2026-10-18T20:05Z · #24384 and #24318 taken in one claim, as triage 6253816310 named (both p2
area:identity, the same file). Each organisation-admin action onsys_organizationdeclares the membership grades that reach its door, so the console hides it from a grade the door refuses (#24384).change_slugis offered only where its door exists (#24318). The shape for the second is chosen by measurement and the four axes.Session:
session_01JtSQQ3EWsbNPzyYMug1tXy(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
os-project-manager(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24384-org-admin-actions-reachWorktree:
objectstack-issue-24384Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/platform-objects/src/identity/sys-organization.object.ts:requiresMembershipReachonrename_organization,update_organizationanddelete_organization, andchange_slug's gate or declaration, by the shape chosen for platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318.domain:specon [PM seat] domain:spec · seat 1 — ⏳ vacant #6017 before the queue:packages/spec/src/identity/membership-reach.ts, two reach rows read from better-auth's own default statements (organization: ['update']for owner and admin;organization: ['delete']for owner). If the four axes choose a newrequiresFeaturegate forchange_slug, itsPUBLIC_AUTH_FEATURESentry (packages/spec/src/kernel/public-auth-features.ts) joins the same declaration.packages/platform-objects/src/pages/sys-organization.page.ts: the stale header-actions comment that platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318's fold-in note 6249926047 names (comment only)..changeset/24384-*.md.leave_organizationstays ungated. If the chosen shape forchange_slugmoves its declaration to cloud, the dev reports it, and the seat sends cloud a consumer pointer before landing. Stop on any other breach and explain it in the report.Container & model:
M,mode:cloud(measured on a booted multi-organization composition: which header and list actions a plain member, an admin and an owner are offered, and each door's answer to them, before and after),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: yes (widening) (provisional:
@objectstack/spec's published reach table gains rows, and possibly a feature key, while the console offers fewer actions; owes oneCONTRACT_REVIEW_TIERreview, on the published-schema face, before the queue)Responsibility: only
invite_userdeclares a membership reach onsys_organization, so the console offers the organisation-admin actions to every grade, andchange_slugtargets a route only cloud serves | the reach table andrequiresMembershipReachalready exist for this (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, ADR-0108 D1), andupdate_organizationis the open-source slug door platform-objects sys_organization: a record page has no door to rename an organization — update_organization is list_item only and bundles slug, which the cloud's change_slug orchestrates #24271 kept | every multi-organization composition: a plain member sees admin buttons the door refuses; outside cloud, Change Slug answers404(platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318, measured on a booted showcase)Thread-read: 6253816310
Serial constraints cleared: open PRs' file lists and the
pm:dispatchedclaims were read at this claim. No open PR touchessys-organization.object.ts,sys-organization.page.ts,membership-reach.tsorpublic-auth-features.ts. PR feat(platform-objects): sys_organization declares a name/logo-only rename action at the record header #24317 (platform-objects sys_organization: a record page has no door to rename an organization — update_organization is list_item only and bundles slug, which the cloud's change_slug orchestrates #24271), which platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318 was serial after, has merged. This seat's data reads: every GET /api/v1/data read makes five serial sys_metadata reads of the target object's own active row (about 250 ms at 50 ms RTT) #24371 (protocol.ts) and core(api-key): resolveApiKeyAdmission turns a failed sys_api_key read into "no key", so a key holder gets 401 during a store outage where a session caller gets 503, and the request falls on to the session arm #24299 (api-key.ts) do not meet this surface.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Dispatch ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-18T20:08Z:mode:cloud. The dev session issession_01JkPx97R4MbdoSQcC5HgGdb. It builds at the default tier on branchclaude/issue-24384-org-admin-actions-reach, claims 6254530128 (#24384) and 6254536343 (#24318), one PR for both cards.area:identity, and platform-objects sys_organization: a record page has no door to rename an organization — update_organization is list_item only and bundles slug, which the cloud's change_slug orchestrates #24271's one-door-per-field ruling.sys_organizationactions a plain member, an admin and an owner are offered on a booted multi-organization composition, and each door's answer to each grade.requiresMembershipReachon the rename, update and delete actions.change_slugis offered only where its door exists. The shape is chosen by the four axes.packages/specrow is declared todomain:spec(6254547379).Fixes #24384andFixes #24318, then anos-dev-reportcomment on platform-objects sys_organization: rename_organization, update_organization, delete_organization and change_slug carry no membership-reach gate, so a plain member is offered organisation-admin buttons the doors refuse #24384. The seat owes oneCONTRACT_REVIEW_TIERreview before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24384,
"issues": [
24384,
24318
],
"status": "needs_decision",
"branch": "claude/issue-24384-org-admin-actions-reach",
"pr": "#24399",
"session": "session_01JkPx97R4MbdoSQcC5HgGdb",
"premise_still_valid": true,
"summary": "#24384 is done in draft PR #24399 (Fixes #24384 only). MEMBERSHIP_REACH gains update_organization (/organization/update, owner+admin) and delete_organization (/organization/delete, owner). sys_organization's update_organization and rename_organization declare requiresMembershipReach 'update_organization', and delete_organization declares 'delete_organization'. A booted multi-org showcase measures a plain member offered only create, set-active and leave, and an admin no longer offered delete. The doors' refusals, NOT MEASURED until now, are measured: member update 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION; member and admin delete 403 YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION; admin and owner update 200; owner delete 200. #24318 needs a maintainer decision, so its shape is not landed and the PR body says '#24318 remains open'. Measured: change_slug answers 404 ENDPOINT_NOT_FOUND, and no existing public signal tells cloud compositions apart. A probe also showed that a top-level action with objectName 'sys_organization' from another stack is NOT attached to the served object, so the cloud-declares-its-own shape (the four axes' pick) has no working route today. Removing change_slug would take the button off cloud's page with no declared way to put it back. The new gate shape (A) is a new gate, which the axes default to no. change_slug stays exactly as on main. No cloud consumer pointer is needed for this PR.",
"tests": "Head dde114a (origin/main 584b422 merged in). spec test: 695 files, 21238 passed, 1 todo. platform-objects test: 76 files, 1176 passed. plugin-auth vitest run src/membership-reach-table.test.ts src/public-feature-registry.test.ts: 32 passed; the reach-table it.each covers the 2 new rows, 27 tests in that file. dogfood vitest run of org-record-admin-reach (new), org-admin-affordance-reach and organization-rename-door: 3 files, 23 passed. BEFORE the fix, on dist built from main 1eae21f: the new file failed 4 of 7 (admin offered delete_organization; member offered update, rename and delete; change_slug 404 ENDPOINT_NOT_FOUND). Ablation 1, via ablation-replace: deleted requiresMembershipReach 'delete_organization' (anchor 1 to 0, blob 304d639c to e36bdf53), rebuilt platform-objects, ablation-dist-preflight --absent exit 0. platform-objects.test.ts delete_organization row red ('features.multiOrgEnabled != false' vs the composed predicate); dogfood 2 red (admin and member offered delete). Restored: blob == HEAD, git diff HEAD empty; rebuilt dist preflight finds the marker in 6 files. This leg ran while change_slug was briefly removed on the branch; the mutated line is identical on the final head. Ablation 2: delete_organization grades widened to OWNER_ADMIN (anchor 1 to 0), rebuilt spec. plugin-auth reach-table red: 'delete_organization: the grades equal ...' got [owner, admin], door [owner], 1 of 27. Restored: blob == HEAD; rebuilt dist reads [owner].",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on dde114a gave 112 commands, all run, each exit captured before any pipe, all exit 0. check:skill-examples and check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, unbuilt packages); after building studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis and service-knowledge, both exit 0. check:slot-lookup was killed once by the batch timeout and exits 0 run alone. --ran with ':: exit N' lines: '112 derived, 112 run, 0 NOT-MEASURED (a DERIVED zero)'. Typecheck exits: spec 0, platform-objects 0, dogfood 0. spec check:generated: 'All 14 generated artifacts are up to date' (gen:docs regenerated content/docs/references/ui/action.mdx earlier through --fix). Local-only: check:dev-prereqs 0, check:skill-frame-freshness 0. check-platform-checklist not run: no export renamed or removed. CI on PR #24399 head dde114a at report time: 38 check-runs, 5 success, 5 skipped, 28 queued or in_progress, none failed. in_progress, not awaited.",
"line_budget": "none set; diff vs origin/main (+/-): .changeset/24384-platform-objects-org-admin-reach.md +12/-0; .changeset/24384-spec-org-reach-rows.md +7/-0; content/docs/references/ui/action.mdx +3/-1 (generated); packages/platform-objects/src/identity/sys-organization-rename-action.test.ts +5/-3; packages/platform-objects/src/identity/sys-organization.object.ts +8/-2; packages/platform-objects/src/pages/sys-organization.page.ts +3/-2; packages/platform-objects/src/platform-objects.test.ts +5/-3; packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts +235/-0; packages/spec/src/identity/membership-reach.ts +10/-0",
"files_changed": [
".changeset/24384-platform-objects-org-admin-reach.md",
".changeset/24384-spec-org-reach-rows.md",
"content/docs/references/ui/action.mdx",
"packages/platform-objects/src/identity/sys-organization-rename-action.test.ts",
"packages/platform-objects/src/identity/sys-organization.object.ts",
"packages/platform-objects/src/pages/sys-organization.page.ts",
"packages/platform-objects/src/platform-objects.test.ts",
"packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts",
"packages/spec/src/identity/membership-reach.ts"
],
"deviations": [
"Dispatch-vs-os-dev conflict: the dispatch asked for 'Fixes #24318'. os-dev forbids Fixes on a card that needs a decision (a merge would silently close it), and os-dev wins. The PR body carries 'Fixes #24384' alone and '#24318 remains open'.",
"Worktree: the shared primary checkout was already on the branch claude/issue-24384-org-admin-actions-reach (at 1eae21f, no unique commits). To create ../objectstack-issue-24384 on that branch name, I ran 'git checkout --detach' in the primary checkout and 'git worktree add --no-track -B ... origin/main'. The primary checkout's HEAD is now detached at 1eae21f.",
"The change_slug removal (#24318 shape B) was implemented, pushed (commits 2eaee0c and 830973d) and then reverted on the branch (dd87e1c) once the probe showed cross-package bound actions do not attach. The branch history keeps both. The final diff does not touch change_slug, public-auth-features.ts, the i18n bundles or the runtime test.",
"A throwaway probe test (packages/qa/dogfood/test/zz-probe-attach.dogfood.test.ts) was written, run and deleted; it was never committed.",
"Commit trailers use the AGENTS.md model-free pair (Co-authored-by: Claude). The harness reminder asked for a model-named Co-Authored-By; AGENTS.md takes precedence.",
"git push ran 8 times to the one branch: the empty-branch probe, WIP pushes and the merge. Counted as the one push stroke."
],
"mcp_calls": "0 — no MCP GitHub tool was called. Reads were unauthenticated REST GETs through the proxy (issues 24384 and 24318 with comments; commit check-runs).",
"api_writes": "2 relay writes plus git push. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: one repository_dispatch, run 39169088069 success, POST /repos/objectstack-ai/objectstack/pulls (draft) then POST /repos//issues/24399/assignees [os-project-manager]; body read back identical, 10188 bytes. (2) This os-dev-report comment through the relay: POST /repos//issues/24384/comments. git push to origin claude/issue-24384-org-admin-actions-reach. No labels written.",
"open_questions": [
{
"question": "#24318: what shape does change_slug take, given a cross-package top-level action (objectName 'sys_organization') is not attached to the served object today?",
"options": [
"A: a new PUBLIC_AUTH_FEATURES key that only the orchestrator's composition sets. Cost: a spec row, a plugin-auth producer option, a drift-guard row and a cloud config change. It is a new gate, so the axes default to no unless the maintainer names it.",
"B: the composition that serves the door declares the action, and the platform object stops declaring it. Prerequisite: a platform mechanism that attaches a bound action to an object another package owns (the spec guidance on ObjectExtensionSchema already promises this). Then cloud declares change_slug and its translations, and the platform drops its copy in the same release pair.",
"B-now: remove it now and accept that cloud's page loses Change Slug until a mechanism exists.",
"C: the open-source composition serves change-slug. This widens scope into orchestrator semantics."
],
"recommendation": "B with its prerequisite. By the axes: business need (cloud is the only consumer), long term (one route, one owner; no cloud-only key in the open contract), AI safety (no declared action whose door is absent), startup scope (no new gate). The prerequisite is a platform mechanism, so this is the maintainer's call."
},
{
"question": "Should change_slug carry a membership-grade term meanwhile?",
"options": [
"No: its door is cloud's org-admin, outside better-auth, and no reach row describes it; reusing update_organization's row would book a non-better-auth door in a table pinned to the vendor.",
"Yes: reuse 'update_organization' as the closest grade set."
],
"recommendation": "No, settled that way here. The grade set belongs to whichever shape #24318 takes."
}
],
"out_of_scope_findings": [
"class: b · Seam: spec:ObjectExtensionSchema.guidance.actions (data/object.zod.ts, the refusal text 'Declare a top-level action with objectName TARGET; defineStack() attaches it to the object') → runtime: mergeActionsIntoObjects (stack.zod.ts), which attaches only to objects in the same stack, and the served /meta/object · reach: public door. On a booted showcase (multiTenant posture-only), a top-level action with objectName 'sys_organization' added to the app stack was absent from GET /meta/object/sys_organization's actions, measured on this branch. The refusal prescribes a route that silently drops the action for an object another package owns. · dedupe words: objectName top-level action not attached platform object; ObjectExtension actions guidance defineStack attaches; bound action cross-package served meta",
"carrier: #24318's decision (option B's prerequisite) · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed ·
domain:engineseat 1 ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-18T23:00ZCONTRACT_REVIEW_TIERrecord 6255881407, PASS, on the published-schema face, at headdde114aba6. ACCEPT 6255899032. Not governed.gh-readonly-queue/main/pr-24399-*branch remains.origin/mainf573976865is onorigin/main. It carries PR #24399's 9 files, +288 / −11.completedthroughFixes #24384;pm:dispatchedis cleared.What
mainnow holds:MEMBERSHIP_REACH(@objectstack/spec) gainsupdate_organization(/organization/update: owner and admin) anddelete_organization(/organization/delete: owner). Both are read off better-auth's own role statements and held equal to the installed door by plugin-auth's reach-table test.sys_organization'supdate_organizationandrename_organizationfollowupdate_organization's grades, anddelete_organizationfollows the owner alone, each still behindmultiOrgEnabled.change_slugis unchanged. platform-objects sys_organization: change_slug targets a cloud-only route, so on an open-source multi-organization composition the record page offers Change Slug and the click answers 404 #24318 stayspm:blockedbehind spec(object-extension): the extension refusal saysdefineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400, which triage graded p1target:v18indomain:spec(6256196520).Noted, below the filing gate (record ③):
positionscome from the active organisation, while both doors check the record's organisation. A principal with different grades in two organisations can be offered a button on the other one's row that the door refuses. This is the mechanisminvite_userand every reach row already carry (ADR-0108 D1). It was read in code only, so it is recorded in the seat post, not filed.The dev session
session_01JkPx97R4MbdoSQcC5HgGdbis archived.Generated by Claude Code