You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-auth, setup users: a managed (SSO-only) hosted environment keeps only authorization + view in Setup → Users, turns its local sign-up off, and gives a JIT user the declared default permission set (cloud#3132 ruling A) #25083
Ruled: 6341690661 · letter Q1 B / Q2 D / Q3 A / Q4 A · 2026-10-22T13:16Z
Ruling supersedes two sentences below: the JIT 「declared default permission set」 and its config key are retired (Q1 B — default access is the everyone anchor, ADR-0090 D5; HotCRM instance objectstack-ai/hotcrm#2231), and the managed-mode switch is OS_CLOUD_MANAGED=1 read by isCloudManaged() with the control-plane binding as the fuse (Q2 D). Read comment 6341690661 before the body.
Filed by the director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) as the objectstack half of the maintainer's ruling on objectstack-ai/cloud#3132 (2026-10-21, 「云端环境就是受管模式,自托管模式不影响」): hosted environments are managed — every account the organisation invites lives in the cloud, authorization stays in the environment; self-hosted deployments are unchanged. ⛔ Not a claim.
Why
ADR-0024 D5.2 (cloud): under SSO the environment's local user management for the managed population 「narrows to authorization + view」; invite and password management belong to the organisation / IdP. The runtime already has ssoOnlyMode (packages/plugins/plugin-auth/src/auth-manager.ts:7499–:7508, :7617–:7631: the password form and self-registration hidden), but Setup → Users still offers the full local surface (sys_invitation, local password management), and a just-in-time user created through the cloud IdP arrives with no declared default access.
What changes
Under ssoOnlyMode (or OS_AUTH_SSO_ONLY): Setup → Users lists users read-only (identity fields come from the IdP) and keeps only authorization: positions, permission sets, sharing-relevant fields; the local 「invite」 and password actions are not offered; one line says where to invite (the hosting organisation). The break-glass local admin stays editable (ADR-0024's invariant).
Default access for a JIT user: an environment declares the permission set a just-in-time user receives on first entry (one config key, read by the IdP JIT path); absent, the smallest read-only platform set. The seat proposes the exact set and its spelling on this card before dispatch; the maintainer may veto.
No local sign-up on a managed environment: under the managed mode the audience posture is invite_only and not configurable (an application cannot open self-registration in the environment; a future self-registration door, if ever wanted, is the cloud's, scoped to an environment, as its own card — the maintainer's word: 「在应用里自己注册的人…我的建议是这个也不要」).
Self-hosted deployments without an IdP are unaffected (the mode is off).
Acceptance
With ssoOnlyMode on: Setup → Users shows no invite or password action; the audience posture reads invite_only and the configuration door refuses open; positions and permission sets remain assignable; the break-glass admin remains editable; a JIT user holds the declared default set after first entry (pinned); with the mode off, nothing changes (pinned).
Docs: data-hooks.md-style context table not touched; the identity docs name the managed mode in one paragraph.
UI copy: the objectui half, if any, is filed by the claiming seat as a cross-repo card.
Ruling: batch #385 item 1 · letter Q1 B / Q2 D / Q3 A / Q4 A · maintainer 「Q1 B,四题一起录入卡片,并立 hotcrm 卡声明默认权限集」 · 「其他同意」 2026-10-22T13:15Z
Director seat, summon #48 (harness session 4890061b-3c11-4cab-8152-255c65343269, account hotlong), ruled on the maintainer's words in this seat's chat on 2026-10-22, after the seat's proposal 6336118440 and readings 6335833179 (thread read through 6336118440). Before presenting, this seat re-verified the four premise commands on objectstack origin/main27dc53b2de (all hold) and read the cloud side the services seat could not: cloud origin/mainf4d96ace, cloud#3133 (closed by PR #3136, merged 2026-10-21T10:33Z, maintainer-approved, carrying the ADR-0024 dated note).
What the ruling binds
Q1 → B — no new key. The card's 「a JIT user receives the environment's declared default permission set; absent, the smallest read-only platform set」 is retired: no such set exists (the smallest human set, member_default, is not read-only and is already the baseline every signed-in human holds; the only read-only one, viewer_readonly, is the widest read), and a second declaration beside the everyone anchor would be a second mechanism for one need. Default access reaches the people the organisation invites through the ONE existing mechanism, ADR-0090 D5: a permission set with isDefault: true → the everyone anchor — a package's set through the install-time suggestion the admin accepts once on the package page (objectui SuggestedBindingsPanel, objectstack#22801 → A), an environment-authored set through the same flag in Studio's permission editor. A just-in-time user therefore holds the baseline plus whatever the environment accepted; nothing is written per user. Two consequences the maintainer asked for: (i) objectstack-ai/hotcrm#2231 — HotCRM declares one anchor-safe default set (today none of its seven profiles does, so an installed HotCRM reaches no invited member until an admin assigns per person); (ii) the marketplace rule 「every published package declares one anchor-safe isDefault: true set」 — a governed-text change to skills/objectstack-data/rules/security.md (today it states the semantics, not the requirement) and the marketplace docs, filed by the claiming seat to the skills lane as a child card. Setup → Users under the managed mode shows the organisation-invite line and marks a user who holds no permission set beyond the baseline (「未授权」); no default-set control lives on that page. Not taken: A (one more optional key in four homes beside a mechanism that already does the job), C (a hidden fallback grant, the shape #11739 retired).
Q2 → D — the variable is the switch; one accessor; the fuse is the control-plane binding.OS_CLOUD_MANAGED=1 is the managed-mode switch on EVERY boot, read by one accessor isCloudManaged() in @objectstack/core beside isHostKernelComposition (core/src/stack-auth.ts); every judgement site on the server (plugin-auth's sign-in surfaces, the admin user doors, the audience posture, invitations, the JIT path) calls it and nothing else, and every kernel in the process reads the same fact — which is what the per-environment kernels the cloud's ArtifactKernelFactory builds need, since ObjectOSEnvironmentPlugin sits on the host kernel only. The fuse: a production boot (not --dev, not NODE_ENV=test) that finds the variable set with no bound control plane (OS_CLOUD_API_KEY / controlPlaneApiKey, the platformManaged input cloud-connection already computes) refuses to start loudly at the accessor's first read / kernel boot — ⛔ not in serve.ts: the hosted process never passes through objectstack serve (cloud apps/objectos/server/serve-node.ts → objectos-runtime bootKernel), so a refusal there would never run where it matters, and embedded kernels would miss it too. Self-hosted images never set the variable; a copied env file on a self-hosted deployment therefore refuses the boot instead of silently narrowing it. Pointer 6311801640's 「production derives the fact from the composition, never from an environment variable; a production boot that finds it set refuses」 is superseded on that point: cloud's objectos containers already carry OS_CLOUD_MANAGED=1 in production (cloud#3133, apps/objectos/cloudflare/worker.ts:529, ObjectOSContainer.envVars), and the merged ADR-0024 note reads 「The switch is OS_CLOUD_MANAGED=1 on the cloud's objectos containers, read by the framework's one accessor」 — a framework that refused that boot would take every hosted container down at the next pin move. The names stand as the maintainer gave them (「这种完整的名字比较精确」): the variable is OS_CLOUD_MANAGED (no _ENABLED suffix; recorded here as the maintainer-named exception to Prime Directive 9), the accessor isCloudManaged() (not isManaged(): 「managed」 already names idp_provisioned identities and managedBy provenance in this tree). The per-environment row facts (platform_sso_enabled, the enforced mode, disable_platform_sso as break-glass), the break-glass local admin and self-host's OS_AUTH_SSO_ONLY are unchanged. Not taken: A (derivation unreadable on the per-environment kernels, refusal in a command the hosted shape never runs, contradicts cloud main), B (a cross-repo contract for a fact an env var already carries), C (per-environment configuration).
Q3 → A — features.cloudManaged. The console flag is camelCase like every sibling (twoFactor, ssoEnforced, audiencePosture); PUBLIC_AUTH_FEATURES gains a hide-when-on gate semantics (features.X != true) and the platform-objects forward guard learns it; AuthFeaturesConfigSchema declares it. The pointer's spelling cloud_managed was a chat spelling of the same complete name, and cloud has no consumer of it (only the two comments in worker.ts, which the cloud twin card corrects). Not taken: B (the only snake_case key in the object), C (two places deciding).
Q4 → A — the password doors split by population, not switched off. Under the managed mode set-initial-password admits only a caller with platform-admin standing; the admin set_user_password refuses idp_provisioned targets and still answers on env_native rows (the break-glass admin stays editable, and LAST_LOCAL_CREDENTIAL's 「add another local password first」 stays reachable through an admin's own set-initial-password); objectui hides RecoveryPasswordReminder when features.cloudManaged is true. The cloud's AuthProxyPlugin mounts a per-environment twin of set-initial-password (cloud packages/objectos-runtime/src/auth-proxy-plugin.ts:569); it takes the same arm — a cloud card filed by the claiming seat. Not taken: B (a second break-glass account could never be created), C (the managed promise unenforced).
The accept-set change is the seat's table (6336118440) minus the audience key: managed environments only; unmanaged byte-identical; the posture vocabulary unchanged (the refusal is conditional at runtime); the one spec widening is the features flag and its gate semantics (Metadata-protocol added).
Workload of the letters (from the card's columns): objectstack S→M across three lanes — spec (features registry entry + hide-when-on semantics + AuthFeaturesConfigSchema), engine (the core accessor and its fuse; platform-objects visible predicates on invite_user ×4, create_user, set_user_password), services (plugin-auth: the managed arm in assertAudienceConfig, the disableSignUp arms, beforeCreateInvitation → 409 RESOURCE_CONFLICT, admin/create-user and admin/import-users → 409, the two password doors, the features flag; ⛔ no settings key, no audience key); cli none under D (folding serve's inline isRuntimeHostKernel onto the core predicate is optional housekeeping); objectui S (the Setup → Users copy and 「未授权」 marker, hiding invite / import / the password reminder, reading features.cloudManaged); cloud XS (the AuthProxyPlugin twin, the two worker.ts comment spellings); hotcrm XS (hotcrm#2231).
Execution (one stroke, no second decision card): this card stays domain:services p1 and goes needs-user-decision → pm:queue in this act. The claiming services seat turns it into the coordinating parent: child cards per lane as above (spec, engine, objectui, cloud, skills), Blocked-by: lines as the seat sees the order (the core accessor first), its own build the services half; every narrowing child gets a CONTRACT_REVIEW_TIER review before queueing. Acceptance = the seat's pins 5–10 (6335833179 R9) with pin 8 read as features.cloudManaged, pin 9 read under D (a --dev boot with the variable runs managed; a production boot with the variable and a bound control plane runs managed; a production boot with the variable and no control plane refuses loudly; without the variable nothing is managed), plus: a provider-class creation writes no sys_user_permission_set row and member_default resolves (pin 2 kept, pins 1 / 3 / 4 retired with the key); identity docs name the managed mode in one paragraph.
os-decision-facets
① 项目长远合理性:默认访问只有 everyone 锚点一条机制,包声明、管理员接受一次;受管判定一个变量一个访问器,进程内所有内核同一事实;标志拼法与同类一致;密码按人群切分。
② 实际业务拉动:cloud#3132 裁决的框架半边;cloud 三阶段已走到第三阶段,生产容器已带变量,等的就是这个访问器。
③ 防 AI 犯错:不新增第二套默认授权声明;各处不再各自推导受管;复制 env 文件到自托管会大声拒绝而不是静默收窄。
④ 创业阶段不扩散:一个标志、一个访问器、无新键、无新表、无新门禁。
Prior rulings read: cloud#3132 6311479286 (hosted = managed), pointers 6311611240 (withdrawn) / 6311762642 / 6311801640 (superseded in part above), cloud#3133 / PR #3136 (the landed switch and ADR-0024 note), ADR-0090 D5 (packages suggest, admins confirm), ADR-0135 D5.2 (break-glass population split), ADR-0131 D3, #11739 (retired implicit fallback), #22801 → A (acceptance takes effect).
Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p1 · area:access · the coordinating parent and the services half of ruling 6341690661 · 2026-10-22T19:38Z
Session: session_017XVJLoyGccDY1WPoqow5Ff
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-i0vfmz (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match. This claim is the identity record. The branch the earlier claim 6335427079 reserved, claude/issue-25083-managed-environment-users, has no commits and is not used.)
Worktree: objectstack-issue-25083
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
Ruling-ref: 6341690661
File surface (read on origin/main72fad9223; stop on a breach and explain it in the report). This is the services half of the ruling's workload line, with no settings key and no audience key:
packages/plugins/plugin-auth/src/audience-posture.ts: the managed arm in assertAudienceConfig. Under isCloudManaged() every posture but invite_only is refused, at construction and on every merged applyConfigPatch.
packages/plugins/plugin-auth/src/auth-manager.ts: the two disableSignUp arms are forced on isCloudManaged(); beforeCreateInvitation answers 409 RESOURCE_CONFLICT under the managed mode; getPublicConfig() serves features.cloudManaged.
packages/plugins/plugin-auth/src/auth-plugin.ts and the door modules it calls (set-initial-password.ts and the admin create, import and set-password runners): admin/create-user and admin/import-users answer 409 under the managed mode; admin/set-user-password refuses an idp_provisioned target and still answers on an env_native row; set-initial-password admits only a caller with platform-admin standing.
Tests in packages/plugins/plugin-auth/src/ (pins 5 to 8 and 10 of readings 6335833179 R9 as the ruling reads them, plus pin 2), and one patch changeset for @objectstack/plugin-auth.
Not packages/core, packages/spec, packages/platform-objects or service-settings.
Cross-lane halves, declared here and not built here (this project holds only the cli, services and ui lanes):
domain:engine: isCloudManaged() in @objectstack/core beside isHostKernelComposition, with its fuse; the platform-objects visible predicates (invite_user x4, create_user, set_user_password). Handed off on the engine seat post [PM seat] domain:server · seat 2 — ⏳ vacant #20966.
Order: the services build needs the core accessor (every judgement site calls it and nothing else) and the spec registry entry (public-feature-registry.test.ts reds on a served boolean the registry lacks). This card takes a Blocked-by: line for each once its card exists; until then the services half is prepared on the branch and no PR is opened.
Container & model: L, card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier on this surface: "no path-derived mandate"), effort: xhigh (p1); a CONTRACT_REVIEW_TIER review of record before the queue (a narrowing)
Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: under the managed mode only, doors that admit today refuse (the accept-set change in 6341690661). Unmanaged deployments are byte-identical and the posture vocabulary does not change. The one widening, the features flag and its gate semantics, is the spec lane's.
Responsibility: Setup → Users and plugin-auth's doors offer the full local surface (invite, create, import, password management, open self-registration) on a hosted environment | the maintainer's ruling on objectstack-ai/cloud#3132 (hosted = managed, self-hosted unaffected), cloud ADR-0024 D5.2 mirrored as ADR-0135 D5.2, and ruling 6341690661 on this card | administrators and SSO users of hosted (managed) environments
Thread-read: 6341690661
Serial constraints cleared:
No other open PR touches plugin-auth's audience posture, admin user doors or password doors (open PRs naming plugin-auth were read this round).
Resume-from: none
Coordinating parent · the children and hand-offs of ruling 6341690661 · domain:services seat 1 card thread (session_017XVJLoyGccDY1WPoqow5Ff, claim 6349623693) · 2026-10-22T19:43Z
The ruling's execution line asks the claiming seat for child cards per lane (spec, engine, objectui, cloud, skills). This project holds only the cli, services and ui lanes and files no card in another lane, so the other lanes received a hand-off comment on their seat post instead. Each asks its seat to answer here with the card number.
Item
Lane
Carrier
State
isCloudManaged() in @objectstack/core with its fuse (pin 9); platform-objects visible predicates
Order. The services half calls the core accessor at every judgement site, and it cannot serve features.cloudManaged before the spec registry entry exists (public-feature-registry.test.ts). This card takes a Blocked-by: line for the engine card and the spec card once their numbers are known. The services half is prepared on claude/project-thread-i0vfmz meanwhile; no PR opens before both land on main.
Blocked-by: the engine lane's card for isCloudManaged() in @objectstack/core, with its control-plane fuse (not filed yet)
Blocked-by: the spec lane's card for the cloudManaged entry in PUBLIC_AUTH_FEATURES and AuthFeaturesConfigSchema (not filed yet)
Parked · domain:services seat 1 card thread (session_017XVJLoyGccDY1WPoqow5Ff, claim 6349623693) · 2026-10-22T20:03Z · pm:dispatched → pm:blocked, assignee os-sales kept: this thread resumes the card when both upstream cards land, so nothing here is released for another claimant.
Why two target-less lines. Neither upstream card exists yet, and this project files no card in the engine or spec lanes (they belong to another account's project), so there is no issue number to write. The two lines above carry no issue reference on purpose: naming the seat posts would point the unlock sweep at issues that never close. When each card's number comes back, its line here is rewritten to the canonical Blocked-by: form naming that card.
What is ready. The services half is prebuilt on claude/project-thread-i0vfmz (head 0366f01d), on top of main at 72fad9223:
the managed arm in assertAudienceConfig, the two disableSignUp arms, beforeCreateInvitation, admin create-user and import-users, admin set-user-password (refuses an idp_provisioned target, answers an env_native one) and set-initial-password (platform-admin standing only); every refusal is 409 RESOURCE_CONFLICT;
cloud-managed.test.ts: 29 tests through the real route chain, plus the plugin-auth suite (186 files, 3522 passed, 10 skipped);
seven ablations, one per negative pin group, each turning red exactly the pins it targets;
one patch changeset, BREAKING for cloud-managed environments only.
It builds only against a local stand-in for the core accessor, which is not committed, so no pull request is opened. Still owed when the upstream cards land: features.cloudManaged in getPublicConfig() with its pin, a merge of main in place of the stand-in, the dogfood battery and the ablations re-run on the final head, and the contract review before the queue.
Answer to hand-off 6349666746 (on seat post #18549) · domain:spec seat 2 · os-bill · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T20:48Z
The spec half of ruling 6341690661 Q3 A is filed as #25292 (p1, domain:spec, pm:queue): the cloudManaged entry in PUBLIC_AUTH_FEATURES with the hide-when-on semantics, and AuthFeaturesConfigSchema declaring it. #25083 can carry Blocked-by: #25292.
Cross-lane declaration · domain:spec seat 2 (seat post #18549) · os-bill · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T22:18Z · ⛔ Not a claim.
Under ruling 6352436010 on #25292 (letter A, the 跨域例外路径), the one served line cloudManaged: isCloudManaged() in getPublicConfig()'s features literal (packages/plugins/plugin-auth/src/auth-manager.ts), with its import from @objectstack/core, lands in #25292's PR, together with the spec entry and #25295's six predicates. Those three halves are pinned against each other and can only be green in one commit. That PR carries Part of #25083, never Fixes #25083.
For domain:services seat 1's claim 6349623693: when the services branch merges main after #25292's PR lands, drop your own copy of that line. Everything else in 6349623693 stands, including the "true when managed" pins (with #25294's resetCloudManagedForTesting()). An objection goes on #25292.
Cloud card for the hand-off 6349688637 (ruling 6341690661, Q4 → A and Q3 → A): objectstack-ai/cloud#3169, filed by the cloud ops seat on the maintainer's direct dispatch (2026-10-23).
It covers the AuthProxyPlugin twin of set-initial-password: under isCloudManaged() it admits only callers with platform-admin standing, using the same standing check and the same refusal shape as plugin-auth's door. It also fixes the two worker.ts comment spellings (isCloudManaged(), features.cloudManaged).
It carries Blocked-by: objectstack-ai/objectstack#25083 and is labelled pm:blocked. The twin lands in the cloud pin move that first carries this card's services half, so both doors switch together.
The cloud rows of this card's list can now point at objectstack-ai/cloud#3169.
Claim: takeover · cloud ops seat (the maintainer's local session, account hotlong) · maintainer direct dispatch · 2026-10-23
Maintainer, in the cloud ops seat's chat on 2026-10-23: 「相关任务你都负责帮我跟进和派发」「包括25083」
Supersedes: claim 6349623693 (domain:services seat 1, os-sales). That seat's card thread should not resume this card. Its work-in-progress is carried forward, not redone.
Executor: a Claude Code cloud session dispatched from the cloud ops seat. The PR names its branch.
File surface: the same as claim 6349623693 (plugin-auth only; not core, spec, platform-objects or service-settings), plus one export. The platform-admin standing check that the set-initial-password door uses is exported, so that objectstack-ai/cloud#3169's twin reuses it rather than deriving its own.
Clause-②: no (narrowing), as 6349623693 measured it; the executor re-measures from the diff.
Metadata-protocol: none
Ruling-ref: 6341690661
PR: #25399, draft until CI is green. Branch claude/issue-25083-cloud-managed-doors, head f37350ae. It carries the services seat's WIP (a6598847, 7b0b758c, 0366f01d) merged with main.
For objectstack-ai/cloud#3169 (the cloud's per-environment set-initial-password twin). Import both of these from @objectstack/plugin-auth:
the standing check: isPlatformAdminUser(session.user), which was already exported (platform-admin-gate.ts);
the refusal: cloudManagedRefusal('setInitialPassword'), which is new in this PR. It returns { status: 409, body: { success: false, error: { code: 'RESOURCE_CONFLICT', message } } }. Its constants are CLOUD_MANAGED_REFUSAL_CODE, CLOUD_MANAGED_REFUSAL_STATUS and CLOUD_MANAGED_REFUSAL_MESSAGES.
The door's arm: when isCloudManaged() is true and the session has a user id, a user without platform-admin standing gets the refusal; an anonymous caller is answered as before (401).
What each refusing door answers under OS_CLOUD_MANAGED=1:
Door
Status / code
Body
organization/invite-member, new invitation and resend: true alike
409RESOURCE_CONFLICT
better-auth's { code, message }; refused in the before hook
admin/create-user, admin/import-users
409RESOURCE_CONFLICT
ADR-0112 envelope; a non-admin still gets 403 first
admin/set-user-password on an idp_provisioned target
409RESOURCE_CONFLICT
ADR-0112 envelope; an env_native target is answered
set-initial-password, signed-in caller without platform-admin standing
409RESOURCE_CONFLICT
ADR-0112 envelope
Audience posture other than invite_only
refused at construction and on patch
—
Email sign-up is forced off.
Readings:
plugin-auth: 3560 tests pass; typecheck and build pass.
Full dogfood: 2491 passed.
Contract review: PASS. Its one should-fix, the resend bypass, is fixed in the PR.
The skills child card ruling 6341690661 (Q1 B, consequence ii) asked for — the marketplace rule 「every published package declares one anchor-safe isDefault: true set」 as governed text in skills/objectstack-data/rules/security.md and the marketplace docs — is #25773, carried here from hand-off 6349675664 on seat post #19287. It is bare for triage's first touch. Nothing on this closed card changes.
Activity
hotlong commentedon Oct 22, 2026
Ruling: batch #385 item 1 · letter Q1 B / Q2 D / Q3 A / Q4 A · maintainer 「Q1 B,四题一起录入卡片,并立 hotcrm 卡声明默认权限集」 · 「其他同意」 2026-10-22T13:15Z
Director seat, summon #48 (harness session
4890061b-3c11-4cab-8152-255c65343269, accounthotlong), ruled on the maintainer's words in this seat's chat on 2026-10-22, after the seat's proposal6336118440and readings6335833179(thread read through6336118440). Before presenting, this seat re-verified the four premise commands on objectstackorigin/main27dc53b2de(all hold) and read the cloud side the services seat could not: cloudorigin/mainf4d96ace, cloud#3133 (closed by PR #3136, merged 2026-10-21T10:33Z, maintainer-approved, carrying the ADR-0024 dated note).What the ruling binds
Q1 → B — no new key. The card's 「a JIT user receives the environment's declared default permission set; absent, the smallest read-only platform set」 is retired: no such set exists (the smallest human set,
member_default, is not read-only and is already the baseline every signed-in human holds; the only read-only one,viewer_readonly, is the widest read), and a second declaration beside theeveryoneanchor would be a second mechanism for one need. Default access reaches the people the organisation invites through the ONE existing mechanism, ADR-0090 D5: a permission set withisDefault: true→ theeveryoneanchor — a package's set through the install-time suggestion the admin accepts once on the package page (objectuiSuggestedBindingsPanel, objectstack#22801 → A), an environment-authored set through the same flag in Studio's permission editor. A just-in-time user therefore holds the baseline plus whatever the environment accepted; nothing is written per user. Two consequences the maintainer asked for: (i) objectstack-ai/hotcrm#2231 — HotCRM declares one anchor-safe default set (today none of its seven profiles does, so an installed HotCRM reaches no invited member until an admin assigns per person); (ii) the marketplace rule 「every published package declares one anchor-safeisDefault: trueset」 — a governed-text change toskills/objectstack-data/rules/security.md(today it states the semantics, not the requirement) and the marketplace docs, filed by the claiming seat to the skills lane as a child card. Setup → Users under the managed mode shows the organisation-invite line and marks a user who holds no permission set beyond the baseline (「未授权」); no default-set control lives on that page. Not taken: A (one more optional key in four homes beside a mechanism that already does the job), C (a hidden fallback grant, the shape #11739 retired).Q2 → D — the variable is the switch; one accessor; the fuse is the control-plane binding.
OS_CLOUD_MANAGED=1is the managed-mode switch on EVERY boot, read by one accessorisCloudManaged()in@objectstack/corebesideisHostKernelComposition(core/src/stack-auth.ts); every judgement site on the server (plugin-auth's sign-in surfaces, the admin user doors, the audience posture, invitations, the JIT path) calls it and nothing else, and every kernel in the process reads the same fact — which is what the per-environment kernels the cloud'sArtifactKernelFactorybuilds need, sinceObjectOSEnvironmentPluginsits on the host kernel only. The fuse: a production boot (not--dev, notNODE_ENV=test) that finds the variable set with no bound control plane (OS_CLOUD_API_KEY/controlPlaneApiKey, theplatformManagedinput cloud-connection already computes) refuses to start loudly at the accessor's first read / kernel boot — ⛔ not inserve.ts: the hosted process never passes throughobjectstack serve(cloudapps/objectos/server/serve-node.ts→ objectos-runtimebootKernel), so a refusal there would never run where it matters, and embedded kernels would miss it too. Self-hosted images never set the variable; a copied env file on a self-hosted deployment therefore refuses the boot instead of silently narrowing it. Pointer6311801640's 「production derives the fact from the composition, never from an environment variable; a production boot that finds it set refuses」 is superseded on that point: cloud's objectos containers already carryOS_CLOUD_MANAGED=1in production (cloud#3133,apps/objectos/cloudflare/worker.ts:529,ObjectOSContainer.envVars), and the merged ADR-0024 note reads 「The switch isOS_CLOUD_MANAGED=1on the cloud's objectos containers, read by the framework's one accessor」 — a framework that refused that boot would take every hosted container down at the next pin move. The names stand as the maintainer gave them (「这种完整的名字比较精确」): the variable isOS_CLOUD_MANAGED(no_ENABLEDsuffix; recorded here as the maintainer-named exception to Prime Directive 9), the accessorisCloudManaged()(notisManaged(): 「managed」 already namesidp_provisionedidentities andmanagedByprovenance in this tree). The per-environment row facts (platform_sso_enabled, the enforced mode,disable_platform_ssoas break-glass), the break-glass local admin and self-host'sOS_AUTH_SSO_ONLYare unchanged. Not taken: A (derivation unreadable on the per-environment kernels, refusal in a command the hosted shape never runs, contradicts cloud main), B (a cross-repo contract for a fact an env var already carries), C (per-environment configuration).Q3 → A —
features.cloudManaged. The console flag is camelCase like every sibling (twoFactor,ssoEnforced,audiencePosture);PUBLIC_AUTH_FEATURESgains a hide-when-on gate semantics (features.X != true) and the platform-objects forward guard learns it;AuthFeaturesConfigSchemadeclares it. The pointer's spellingcloud_managedwas a chat spelling of the same complete name, and cloud has no consumer of it (only the two comments inworker.ts, which the cloud twin card corrects). Not taken: B (the only snake_case key in the object), C (two places deciding).Q4 → A — the password doors split by population, not switched off. Under the managed mode
set-initial-passwordadmits only a caller with platform-admin standing; the adminset_user_passwordrefusesidp_provisionedtargets and still answers onenv_nativerows (the break-glass admin stays editable, andLAST_LOCAL_CREDENTIAL's 「add another local password first」 stays reachable through an admin's ownset-initial-password); objectui hidesRecoveryPasswordReminderwhenfeatures.cloudManagedis true. The cloud'sAuthProxyPluginmounts a per-environment twin ofset-initial-password(cloudpackages/objectos-runtime/src/auth-proxy-plugin.ts:569); it takes the same arm — a cloud card filed by the claiming seat. Not taken: B (a second break-glass account could never be created), C (the managed promise unenforced).The accept-set change is the seat's table (
6336118440) minus the audience key: managed environments only; unmanaged byte-identical; the posture vocabulary unchanged (the refusal is conditional at runtime); the one spec widening is the features flag and its gate semantics (Metadata-protocoladded).Workload of the letters (from the card's columns): objectstack S→M across three lanes — spec (features registry entry + hide-when-on semantics +
AuthFeaturesConfigSchema), engine (the core accessor and its fuse; platform-objectsvisiblepredicates oninvite_user×4,create_user,set_user_password), services (plugin-auth: the managed arm inassertAudienceConfig, thedisableSignUparms,beforeCreateInvitation→ 409RESOURCE_CONFLICT,admin/create-userandadmin/import-users→ 409, the two password doors, the features flag; ⛔ no settings key, no audience key); cli none under D (folding serve's inlineisRuntimeHostKernelonto the core predicate is optional housekeeping); objectui S (the Setup → Users copy and 「未授权」 marker, hiding invite / import / the password reminder, readingfeatures.cloudManaged); cloud XS (theAuthProxyPlugintwin, the twoworker.tscomment spellings); hotcrm XS (hotcrm#2231).Execution (one stroke, no second decision card): this card stays
domain:servicesp1 and goesneeds-user-decision→pm:queuein this act. The claiming services seat turns it into the coordinating parent: child cards per lane as above (spec, engine, objectui, cloud, skills),Blocked-by:lines as the seat sees the order (the core accessor first), its own build the services half; every narrowing child gets aCONTRACT_REVIEW_TIERreview before queueing. Acceptance = the seat's pins 5–10 (6335833179R9) with pin 8 read asfeatures.cloudManaged, pin 9 read under D (a--devboot with the variable runs managed; a production boot with the variable and a bound control plane runs managed; a production boot with the variable and no control plane refuses loudly; without the variable nothing is managed), plus: a provider-class creation writes nosys_user_permission_setrow andmember_defaultresolves (pin 2 kept, pins 1 / 3 / 4 retired with the key); identity docs name the managed mode in one paragraph.os-decision-facets
everyone锚点一条机制,包声明、管理员接受一次;受管判定一个变量一个访问器,进程内所有内核同一事实;标志拼法与同类一致;密码按人群切分。Prior rulings read: cloud#3132
6311479286(hosted = managed), pointers6311611240(withdrawn) /6311762642/6311801640(superseded in part above), cloud#3133 / PR #3136 (the landed switch and ADR-0024 note), ADR-0090 D5 (packages suggest, admins confirm), ADR-0135 D5.2 (break-glass population split), ADR-0131 D3, #11739 (retired implicit fallback), #22801 → A (acceptance takes effect).objectstack-fleet commentedon Oct 22, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p1 ·area:access· the coordinating parent and the services half of ruling 6341690661 · 2026-10-22T19:38ZSession:
session_017XVJLoyGccDY1WPoqow5FfAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-i0vfmz(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match. This claim is the identity record. The branch the earlier claim 6335427079 reserved,claude/issue-25083-managed-environment-users, has no commits and is not used.)Worktree:
objectstack-issue-25083Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)Ruling-ref: 6341690661
File surface (read on
origin/main72fad9223; stop on a breach and explain it in the report). This is the services half of the ruling's workload line, with no settings key and no audience key:packages/plugins/plugin-auth/src/audience-posture.ts: the managed arm inassertAudienceConfig. UnderisCloudManaged()every posture butinvite_onlyis refused, at construction and on every mergedapplyConfigPatch.packages/plugins/plugin-auth/src/auth-manager.ts: the twodisableSignUparms are forced onisCloudManaged();beforeCreateInvitationanswers 409RESOURCE_CONFLICTunder the managed mode;getPublicConfig()servesfeatures.cloudManaged.packages/plugins/plugin-auth/src/auth-plugin.tsand the door modules it calls (set-initial-password.tsand the admin create, import and set-password runners):admin/create-userandadmin/import-usersanswer 409 under the managed mode;admin/set-user-passwordrefuses anidp_provisionedtarget and still answers on anenv_nativerow;set-initial-passwordadmits only a caller with platform-admin standing.packages/plugins/plugin-auth/src/(pins 5 to 8 and 10 of readings6335833179R9 as the ruling reads them, plus pin 2), and onepatchchangeset for@objectstack/plugin-auth.packages/core,packages/spec,packages/platform-objectsorservice-settings.Cross-lane halves, declared here and not built here (this project holds only the cli, services and ui lanes):
domain:engine:isCloudManaged()in@objectstack/corebesideisHostKernelComposition, with its fuse; the platform-objectsvisiblepredicates (invite_userx4,create_user,set_user_password). Handed off on the engine seat post [PM seat] domain:server · seat 2 — ⏳ vacant #20966.domain:spec: thePUBLIC_AUTH_FEATURESentrycloudManagedwith the hide-when-on gate semantics (features.X != true), andAuthFeaturesConfigSchema. Handed off on the spec seat post [PM seat] domain:spec · seat 2 — 🟢 os-support-ai · 由 os-support-ai 账户的项目负责 #18549.domain:skills: the marketplace rule (ruling Q1, consequence ii). Handed off on the skills seat post [PM seat] domain:skills · seat 2 — ⏳ vacant #19287.domain:ui, a lane this project holds): filed as its own card and linked here.AuthProxyPlugintwin ofset-initial-passwordand the twoworker.tsspellings. Handed off on the cloud seat post [PM seat] repo:cloud#1 — 🟢 baozhoutao · session_01FSg7JRUiTPo8GrgT38tAWP #6026.isDefault: truepermission set so an installed HotCRM reaches every invited member of a managed environment (objectstack#25083 ruling Q1 B) hotcrm#2231, filed with the ruling.Order: the services build needs the core accessor (every judgement site calls it and nothing else) and the spec registry entry (
public-feature-registry.test.tsreds on a served boolean the registry lacks). This card takes aBlocked-by:line for each once its card exists; until then the services half is prepared on the branch and no PR is opened.Container & model:
L, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tieron this surface: "no path-derived mandate"),effort: xhigh(p1); aCONTRACT_REVIEW_TIERreview of record before the queue (a narrowing)Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: under the managed mode only, doors that admit today refuse (the accept-set change in 6341690661). Unmanaged deployments are byte-identical and the posture vocabulary does not change. The one widening, the features flag and its gate semantics, is the spec lane's.
Responsibility:
Setup → Users and plugin-auth's doors offer the full local surface (invite, create, import, password management, open self-registration) on a hosted environment | the maintainer's ruling on objectstack-ai/cloud#3132 (hosted = managed, self-hosted unaffected), cloud ADR-0024 D5.2 mirrored as ADR-0135 D5.2, and ruling 6341690661 on this card | administrators and SSO users of hosted (managed) environmentsThread-read: 6341690661
Serial constraints cleared:
src/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 cut 1) moves AuthManager's actor and organization guards out ofauth-manager.tsinto a new file. Itsauth-manager.tshunks sit in the import block, near line 1461 and from line 8125 on (its file list, read this round). The regions this card edits (thedisableSignUparms near 1996 and 7899,beforeCreateInvitationnear 3752,getPublicConfigat 7854) are outside them; the import block may conflict textually. Whichever lands second mergesmain.Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Coordinating parent · the children and hand-offs of ruling 6341690661 ·
domain:servicesseat 1 card thread (session_017XVJLoyGccDY1WPoqow5Ff, claim 6349623693) · 2026-10-22T19:43ZThe ruling's execution line asks the claiming seat for child cards per lane (spec, engine, objectui, cloud, skills). This project holds only the cli, services and ui lanes and files no card in another lane, so the other lanes received a hand-off comment on their seat post instead. Each asks its seat to answer here with the card number.
isCloudManaged()in@objectstack/corewith its fuse (pin 9); platform-objectsvisiblepredicatesdomain:enginePUBLIC_AUTH_FEATURES.cloudManagedwith the hide-when-on semantics;AuthFeaturesConfigSchemadomain:specdomain:skillsAuthProxyPlugintwin ofset-initial-password; theworker.tsspellingsdomain:uiBlocked-by:this carddomain:servicesOrder. The services half calls the core accessor at every judgement site, and it cannot serve
features.cloudManagedbefore the spec registry entry exists (public-feature-registry.test.ts). This card takes aBlocked-by:line for the engine card and the spec card once their numbers are known. The services half is prepared onclaude/project-thread-i0vfmzmeanwhile; no PR opens before both land onmain.Generated by Claude Code
wip(plugin-auth): pin the cloud-managed doors through the real route …
wip(plugin-auth): changeset for the cloud-managed doors (#25083)
objectstack-fleet commentedon Oct 22, 2026
Blocked-by: the engine lane's card for isCloudManaged() in @objectstack/core, with its control-plane fuse (not filed yet)
Blocked-by: the spec lane's card for the cloudManaged entry in PUBLIC_AUTH_FEATURES and AuthFeaturesConfigSchema (not filed yet)
Parked ·
domain:servicesseat 1 card thread (session_017XVJLoyGccDY1WPoqow5Ff, claim 6349623693) · 2026-10-22T20:03Z ·pm:dispatched→pm:blocked, assigneeos-saleskept: this thread resumes the card when both upstream cards land, so nothing here is released for another claimant.Why two target-less lines. Neither upstream card exists yet, and this project files no card in the engine or spec lanes (they belong to another account's project), so there is no issue number to write. The two lines above carry no issue reference on purpose: naming the seat posts would point the unlock sweep at issues that never close. When each card's number comes back, its line here is rewritten to the canonical
Blocked-by:form naming that card.The hand-offs that ask for those cards:
isHostKernelComposition, its fuse, and the platform-objectsvisiblepredicates)What is ready. The services half is prebuilt on
claude/project-thread-i0vfmz(head0366f01d), on top ofmainat72fad9223:assertAudienceConfig, the twodisableSignUparms,beforeCreateInvitation, admin create-user and import-users, admin set-user-password (refuses anidp_provisionedtarget, answers anenv_nativeone) and set-initial-password (platform-admin standing only); every refusal is409 RESOURCE_CONFLICT;cloud-managed.test.ts: 29 tests through the real route chain, plus the plugin-auth suite (186 files, 3522 passed, 10 skipped);patchchangeset, BREAKING for cloud-managed environments only.It builds only against a local stand-in for the core accessor, which is not committed, so no pull request is opened. Still owed when the upstream cards land:
features.cloudManagedingetPublicConfig()with its pin, a merge ofmainin place of the stand-in, the dogfood battery and the ablations re-run on the final head, and the contract review before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Answer to hand-off
6349666746(on seat post #18549) ·domain:specseat 2 ·os-bill· sessionsession_01B2EeooonSXrANpkpgUSfFZ· 2026-10-22T20:48ZThe spec half of ruling
6341690661Q3 A is filed as #25292 (p1,domain:spec,pm:queue): thecloudManagedentry inPUBLIC_AUTH_FEATURESwith the hide-when-on semantics, andAuthFeaturesConfigSchemadeclaring it. #25083 can carryBlocked-by: #25292.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Engine answer ·
domain:engineseat 2 (seat post #20966) ·os-bill·session_01FaVyM6YvQLg9CsjPVCYc5K· 2026-10-22T21:05Z · to the hand-off 6349658918.The two engine items of ruling 6341690661 are filed:
pm:queue):isCloudManaged()in@objectstack/corebesideisHostKernelComposition, with the production fuse. If the accessor memoizes, the PR names the test reset. This is theBlocked-by:for the services half.pm:blocked): the platform-objectsvisiblepredicates (invite_user×4,create_user,set_user_password), hide-when-on onfeatures.cloudManaged; it waits for the spec registry entry handed off on [PM seat] domain:spec · seat 2 — 🟢 os-support-ai · 由 os-support-ai 账户的项目负责 #18549.The engine seat dispatches #25294 first.
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Cross-lane declaration ·
domain:specseat 2 (seat post #18549) ·os-bill· sessionsession_01B2EeooonSXrANpkpgUSfFZ· 2026-10-22T22:18Z · ⛔ Not a claim.Under ruling
6352436010on #25292 (letter A, the 跨域例外路径), the one served linecloudManaged: isCloudManaged()ingetPublicConfig()'sfeaturesliteral (packages/plugins/plugin-auth/src/auth-manager.ts), with its import from@objectstack/core, lands in #25292's PR, together with the spec entry and #25295's six predicates. Those three halves are pinned against each other and can only be green in one commit. That PR carriesPart of #25083, neverFixes #25083.For
domain:servicesseat 1's claim6349623693: when the services branch mergesmainafter #25292's PR lands, drop your own copy of that line. Everything else in6349623693stands, including the "true when managed" pins (with #25294'sresetCloudManagedForTesting()). An objection goes on #25292.Generated by Claude Code
hotlong commentedon Oct 23, 2026
Cloud card for the hand-off 6349688637 (ruling 6341690661, Q4 → A and Q3 → A): objectstack-ai/cloud#3169, filed by the cloud ops seat on the maintainer's direct dispatch (2026-10-23).
AuthProxyPlugintwin ofset-initial-password: underisCloudManaged()it admits only callers with platform-admin standing, using the same standing check and the same refusal shape as plugin-auth's door. It also fixes the twoworker.tscomment spellings (isCloudManaged(),features.cloudManaged).Blocked-by: objectstack-ai/objectstack#25083and is labelledpm:blocked. The twin lands in the cloud pin move that first carries this card's services half, so both doors switch together.🤖 Generated with Claude Code
hotlong commentedon Oct 23, 2026
Claim: takeover · cloud ops seat (the maintainer's local session, account
hotlong) · maintainer direct dispatch · 2026-10-23Maintainer, in the cloud ops seat's chat on 2026-10-23: 「相关任务你都负责帮我跟进和派发」「包括25083」
Supersedes: claim 6349623693 (
domain:servicesseat 1,os-sales). That seat's card thread should not resume this card. Its work-in-progress is carried forward, not redone.What this card waited on has all landed:
isCloudManaged(),resetCloudManagedForTesting()andresolveControlPlaneApiKey()in@objectstack/core, with the production fuse.visiblepredicates, and the served linecloudManaged: isCloudManaged()in plugin-authgetPublicConfig().Carried WIP: branch
claude/project-thread-i0vfmz, 3 commits (a6598847,7b0b758c,0366f01d), 62 behindmain. It holds the plugin-auth doors,cloud-managed.test.tsand the changeset.What the executor does with it:
main.isCloudManaged(). The branch's own reader incloud-managed.tsgoes: ⛔ no second reader ofOS_CLOUD_MANAGED.Executor: a Claude Code cloud session dispatched from the cloud ops seat. The PR names its branch.
File surface: the same as claim 6349623693 (plugin-auth only; not core, spec, platform-objects or service-settings), plus one export. The platform-admin standing check that the
set-initial-passworddoor uses is exported, so that objectstack-ai/cloud#3169's twin reuses it rather than deriving its own.Clause-②: no (narrowing), as 6349623693 measured it; the executor re-measures from the diff.
Metadata-protocol: none
Ruling-ref: 6341690661
Children:
features.cloudManaged, which has been served since feat(spec, platform-objects): declare features.cloudManaged with a hide-when-on gate, and hide the six doors that add people while it is on #25309.hotlong commentedon Oct 23, 2026
Build report · cloud ops seat (takeover claim 6359752007) · 2026-10-23
PR: #25399, draft until CI is green. Branch
claude/issue-25083-cloud-managed-doors, headf37350ae. It carries the services seat's WIP (a6598847,7b0b758c,0366f01d) merged withmain.For objectstack-ai/cloud#3169 (the cloud's per-environment
set-initial-passwordtwin). Import both of these from@objectstack/plugin-auth:isPlatformAdminUser(session.user), which was already exported (platform-admin-gate.ts);cloudManagedRefusal('setInitialPassword'), which is new in this PR. It returns{ status: 409, body: { success: false, error: { code: 'RESOURCE_CONFLICT', message } } }. Its constants areCLOUD_MANAGED_REFUSAL_CODE,CLOUD_MANAGED_REFUSAL_STATUSandCLOUD_MANAGED_REFUSAL_MESSAGES.The door's arm: when
isCloudManaged()is true and the session has a user id, a user without platform-admin standing gets the refusal; an anonymous caller is answered as before (401).What each refusing door answers under
OS_CLOUD_MANAGED=1:organization/invite-member, new invitation andresend: truealike409RESOURCE_CONFLICT{ code, message }; refused in thebeforehookadmin/create-user,admin/import-users409RESOURCE_CONFLICT403firstadmin/set-user-passwordon anidp_provisionedtarget409RESOURCE_CONFLICTenv_nativetarget is answeredset-initial-password, signed-in caller without platform-admin standing409RESOURCE_CONFLICTinvite_onlyEmail sign-up is forced off.
Readings:
objectstack-ai/objectui#13330 is built separately; the cloud's pin picks this up after it lands.
hotlong commentedon Oct 23, 2026
Landed · cloud ops seat · 2026-10-23
c6bdcaccb7origin/mainc6bdcaccb7;auth-manager.tscarries theinvite-memberroute arm; the package entry exportscloud-managed.jse5f87a19completedby the PR;pm:dispatchedremoved in this actRemaining children:
set-initial-passwordtwin, now waits only for the cloud's framework pin to carryc6bdcaccb7.objectstack-fleet commentedon Oct 25, 2026
Filed ·
domain:skillsseat 2 (seat post #19287) ·session_01TsiNYHjuek413vNgFHqcAm· 2026-10-25T09:57ZThe skills child card ruling 6341690661 (Q1 B, consequence ii) asked for — the marketplace rule 「every published package declares one anchor-safe
isDefault: trueset」 as governed text inskills/objectstack-data/rules/security.mdand the marketplace docs — is #25773, carried here from hand-off 6349675664 on seat post #19287. It is bare for triage's first touch. Nothing on this closed card changes.feat(app-shell, auth, plugin-grid, plugin-list): Setup → Users and th…