Skip to content

Walled postures must not elevate the first self-registrant: platform admin comes ONLY from an env-declared owner email; self-signups never join the Default Organization — ruled fix for cloud#1509 #11184

Description

@os-zhuang

Filed by the triage seat as the framework leg of objectstack-ai/cloud#1509 (measured p0: on a walled multi-tenant SaaS with OS_TENANCY_POSTURE=isolated + OS_AUTH_MEMBERSHIP_POLICY=invite-only, the FIRST self-registrant received platform_admin + isPlatformAdmin: true and was merged into the deployment's Default Organization — full curl evidence on the cloud card).

Maintainer ruling (2026-08-23, live PM chat, verbatim: 「1509 选择 env 指定 owner 邮箱」— option A with the env-owner-email bootstrap):

  1. Under walled postures (isolated / group), the "first registrant becomes owner/platform admin" path is REMOVED. Platform admin is granted only to the account matching an env-declared owner email (exact variable name is the implementer's to propose in the PR — follow existing OS_* naming conventions and document it in the composed template).
  2. Under walled + invite-only, NO self-registrant is auto-merged into the Default Organization — it belongs to the operator only. (Second registrant behavior measured today — 0 orgs, routed to create-workspace — is the correct shape for ALL self-registrants including the first.)
  3. Single-org posture keeps "first user is owner" — that default is ruled reasonable and out of scope here.
  4. Fail-closed clause: walled posture + no owner email declared ⇒ refuse startup with a message naming the variable (matching the existing walled "undeclared knob ⇒ refuse" style), rather than silently reverting to first-registrant elevation.

Establish-first

Locate the elevation site(s) (plugin-auth bootstrap / first-user logic) and the Default-Org auto-join site before writing; report the map in the PR. Pin both directions: (a) walled + declared owner email ⇒ only that account elevates, self-signups get positions: ["user"] and no org; (b) single-org posture unchanged.

Security surface: dispatch per lane discipline at judgment tier; refusal cases carry code+status.

Downstream: objectstack-ai/cloud#1509 is the acceptance leg (blocked on this card) — composed template docs (composed.env.example wording) and the live re-verification happen there after the pin bump.

Refs: objectstack-ai/cloud#1509 (measurement + ruling record).

Activity

  1. added
    bugSomething isn't working
    priority:p0Critical: blocker, must ship before MVP
    on Aug 23, 2026
  2. added theissue type on Aug 23, 2026
  3. claude commented on Aug 23, 2026

    @claude
    Contributor

    Contract review — PASS (skills seat, session session_01RMTpSRF5CjMmQBFfPtPCwJ, standing delegation: maintainer 2026-08-23 「要不还是你挂个定时处理审核吧」). Tier fuse read this sub-round: get_session → external_metadata.last_served_model: claude-fable-5 = CONTRACT_REVIEW_TIER (claude-fable-5, read from dispatch-gates.mjs:2816 on origin/main, not recalled). Reviewer is not the dispatching seat.

    Contract increment of PR #11211 reviewed against the four ruling clauses: (1) walled elevation is keyed solely to the env-declared owner — found by email query regardless of arrival order; the case-variant miss direction is a REFUSAL, never a wrongful elevation; (2) in-repo, the Default-Org bind follows the platform admin, so clause 2 derives from clause 1 (cloud acceptance leg re-verifies the composed deployment); (3) single unchanged, with the over-denial guard pinned (a declared owner does not redirect single-org promotion); (4) fail-closed lands twice — boot refusal in plugin-auth init() naming the variable, and the defense-in-depth elevation refusal keyed to the REQUESTED posture so degraded runs still refuse (the stricter direction). Accept-set moves only toward rejection; machine-checkable refusal reasons; both directions pinned; source-resolution redirects documented; engine-double-contract pin registered. Clearing needs:contract-review on card and PR in the same stroke.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions