Skip to content

feat(auth,platform-objects): org join link V1 — sys_join_link + create/rotate/revoke/info/join, membership via governed addMember #11587

Closed as not planned

Description

@os-zhuang

Part of #11586 (epic — ruling and V1 scope live there; read it first).

Deliverable

The backend half of the universal org join link, V1:

Data — new platform object (suggested name sys_join_link; naming per sibling conventions in packages/platform-objects/src/identity/): organization_id (lookup sys_organization), token (opaque, ≥128-bit random, URL-safe), role semantics PINNED to member (not authorable in V1 — a constant, not a field, unless the schema layer makes a fixed field cheaper to audit), expires_at (default: creation +7d), revoked_at (nullable), max_uses (nullable), use_count, created_by. Exactly ONE active (non-revoked, non-expired) link per org — enforce at the write endpoint AND with a partial unique index if the driver supports it. Generic CRUD suppressed (managed via endpoints only, like sys_invitation).

Endpoints (mounted beside the better-auth organization surface; exact mount point is implementation detail):

Contract bookkeeping — Clause-② YES (this widens the public API surface): book any new feature-gated inputs in PUBLIC_AUTH_FEATURES (packages/spec/src/kernel/public-auth-features.ts, organization entry) per the bidirectional feature-gate guard; new error codes go wherever the error-code ledger requires. Expect the needs:contract-review chain regardless of dispatch tier (subagent tier is not self-provable).

Tests — authz matrix (stranger / plain member / admin / owner) on all mutating endpoints; expiry / revocation / rotation / exhaustion; verified-email gate on join; an assertion that join goes through the addMember path (not a raw insert); idempotent re-join; token-info leak surface (no org internals for invalid tokens).

Token storage note (PM suggestion, dev judges): retrievable-by-admin plaintext in the org-scoped row is acceptable for V1 (member-grade, expiring, revocable — unlike an OAuth client_secret); if hashed-at-rest with show-once is trivially compatible with the console's "copy link anytime" UX, flag the option in the report rather than deciding silently.

Non-goals (V1, ruled on the epic)

Domain allowlists, approval queue, per-link roles, multiple concurrent links, SCIM interplay.

Consumers to unblock

objectui (console dialog + /join/:token route) and cloud (control-plane org-scoping + E2E) sub-issues carry Blocked-by: pointing here — their cards state what they need: stable endpoint paths + error codes in this card's PR body.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    on Aug 24, 2026
  2. self-assigned this
    on Aug 24, 2026
  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 24, 2026
  4. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 1 (epic #11586, maintainer direct-dispatch channel)
    Session: session_71836b57-5db6-459d-ae4d-c6d9d7dd2cc6
    Branch: claude/issue-11587-org-join-link-v1
    Worktree: objectstack-issue-11587
    Domain: domain:services
    File surface: packages/plugins/plugin-auth/src/** (endpoints + wiring), packages/platform-objects/src/identity/** (new object + tests), packages/spec/src/kernel/public-auth-features.ts (gate booking; error-code ledger if new codes require it) (stop on breach; explain in the report)
    Container & model: L, mode:subagent, model: claude-fable-5 — Clause-② mandatory tier (widens the public API surface); dispatch-gates --tier is a floor, the content limb mandates the ceiling here
    Clause-②: yes
    Serial constraints cleared: open PRs scanned — #11560 / #11406 touch packages/spec parse layer (different files than the ledger; no overlap), no open PR touches plugin-auth or platform-objects identity (#11530 and #11562 both MERGED — same-day churn on sys-member.object.ts/public-auth-features.ts, so base on fresh origin/main and re-read those files). Sibling sub-issues objectui#5961 / cloud#1633 are pm:blocked on this card — never same batch.
    Note: mode:subagent tier is not self-provable ⇒ needs:contract-review chain applies at ACCEPT regardless of the dispatched tier.

    Audit (direct-dispatch): maintainer instruction, 2026-08-24 chat, quoted verbatim: 「可以,你开一个epic任务,用objectstack项目经理技能统一派发处理」. Epic delegation recorded on the parent; the spec-adjacent booking is dispatched by the epic PM under that instruction rather than transferred to the spec seat (deviation recorded on the parent).

  5. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Container & model correction: model: claude-fable-5 → model: opus, availability exemption.

    Reading: three consecutive dispatch attempts (one fresh + two SendMessage revivals, 5-minute backoff between) each terminated mid-run by API 529 Overloaded, with ZERO durable output (no worktree, no remote branch — verified before each revival). fable is measurably unavailable right now; per the maintainer's standing exemption (2026-08-13, verbatim): 「fable 如果用完了,可以用 opus」. Not downgrading further.

    Clause-② remains yes and is compensated as designed: the needs:contract-review chain at ACCEPT runs at CONTRACT_REVIEW_TIER regardless of the implementation tier (the review exists precisely to compensate a below-floor dispatch). Re-dispatching a fresh agent now; branch/worktree names unchanged from the claim.

  6. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Dispatch paused — upstream provider incident. Four consecutive dev runs (3× fable, 1× opus after the recorded downgrade) were killed mid-run by API 529 Overloaded with zero durable output. status.claude.com has an active incident: "elevated errors on requests to Claude Mythos 5, Claude Fable 5, Claude Opus 5, and other Claude models" (Investigating 05:06 UTC, Identified 05:27 UTC, 2026-08-24). This is infra-wide, not tier-specific — the earlier fable→opus downgrade reading is superseded by this one; the model choice will be re-evaluated at resume (fable if available, per the original Clause-② mandate). Claim stands; a recovery timer is set to resume dispatch when the incident clears.

  7. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Resuming dispatch on a live probe reading (not on the status page, which still shows the 05:27 UTC "Identified" entry): a minimal fable-tier subagent round-trip just succeeded, so the channel is usable — the incident is an elevated error RATE, not an outage. Model returns to claude-fable-5 per the Clause-② mandate, as promised in the pause comment. The dev is instructed to push early and commit/push after each unit so progress survives any further intermittent kill.

  8. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    PAUSED by the maintainer — the audience assumption behind this epic was corrected.

    Maintainer, 2026-08-24 chat, quoted verbatim: 「协作者不会多,我在云端手工加入都可以,主要是 业务用户怎么方便的注册进来,你先和我讨论方案。」

    This epic builds the cloud-organization join link — membership in the control plane, i.e. the COLLABORATOR layer (env list, development, billing visibility). The maintainer states that layer is low-volume and manual invitation is acceptable, so the epic's justification is withdrawn for now. The real need is the business-user layer: convenient self-registration into a tenant ENVIRONMENT (e.g. steedos.objectos.ai), which is a different runtime, different tables, different auth surface — the code here does not transfer.

    Dispatch stopped mid-run; branch claude/issue-11587-org-join-link-v1 exists with partial work and is left in place. No PR was opened.

    Restart-when: the business-user onboarding design (under discussion with the maintainer 2026-08-24) concludes and the maintainer rules that a cloud-org join link is still wanted.

    Live readings that informed the correction (measured on steedos.objectos.ai /api/v1/auth/config, 2026-08-24): emailPassword.disableSignUp=false, requireEmailVerification=false, socialProviders=[objectstack-cloud], features.ssoEnforced=false, tenancyPosture=single. Env-side self-registration is open and unverified today, and reconcileMembership (policy default auto) auto-binds each new user into the env's single org — that is the surface the business-user design must address.

  9. added
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 24, 2026
  10. removed their assignment
    on Aug 24, 2026
  11. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    ContributorAuthor

    Closing not planned — the hold's Restart-when: condition has fired, and the answer is "not wanted now".

    That condition was: the business-user onboarding design concludes and the maintainer rules whether a cloud-org join link is still wanted. The design concluded today as #11723 (audience posture, ruled and dispatched). The maintainer's ruling on the collaborator layer, quoted verbatim from the 2026-08-24 chat, is:

    「协作者不会多,我在云端手工加入都可以,主要是 业务用户怎么方便的注册进来,你先和我讨论方案。」

    Manual invitation covers the collaborator (cloud-organization) layer at current volume, so this epic has no pull. Per the maintainer's standing guidance (2026-08-16, verbatim: 「我建议有些暂时不准备开发的应该直接关闭。」), work with no firable restart condition is closed rather than held — the card IS the record, and reopening is free.

    Preserved for whoever revives this: the V1 token shape reasoned out here (one active link per org, role pinned to member, default 7-day expiry, revoke/rotate, optional max-uses, membership written through the governed addMember path, verified-email posture per better-auth GHSA-fmh4-wcc4-5jm3) is directly reusable if a link value is ever added to the audience-posture vocabulary in #11723. Branch claude/issue-11587-org-join-link-v1 holds partial work; no PR was opened.

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

    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions