Skip to content

feat(platform-objects,spec): a single-organization deployment can rename its organization - #25661

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-xvfy21
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-xvfy21

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #25347

Clause-②: yes
Metadata-protocol: none

Ruling-ref: 6384629949 (maintainer ruling A, 「25347 同意」). Claim of record: 6384658457 on the card (card thread; the first PR #25464 landed the help and Branding half as 832a84eb). Branch claude/project-thread-xvfy21 is the harness-pinned branch for this session. It names no issue, so the Duplicate Fix Guard's branch-name hint will not match, and the claim is the identity record.

Why

Under the single posture the one organization could not be renamed anywhere. update_organization and rename_organization were both gated on multiOrgEnabled, and the generic edit form keeps name readonly (ADR-0092 D4). So the record kept "Default Organization" for good, which is the third name in the card's report. Renaming the one organization changes nothing about tenancy, so it is not a multi-organization action.

What changes

Outside the claim's file surface

This card is domain:services. One file belongs to the spec lane: packages/spec/src/kernel/public-auth-features.ts. It changes by +5 / −3: one row moved between two gatedInputs lists, with its comment. Maintainer ruling 6384629949 on #25347 puts it in this PR, for this card only; standing rule G is not decided. A cross-lane declaration is posted on spec seat post #6017. The spec face owes a CONTRACT_REVIEW_TIER contract review before the queue. The build is mode:cloud, because the surface spans packages/spec/src/** and another package.

Pins

  • sys-organization-rename-action.test.ts: the gate case now pins rename_organization on (reach) && features.organization != false, with update_organization on multiOrgEnabled as the control.
  • platform-objects.test.ts:
    • The gate-matrix row for rename_organization is now ${ADMINS} && ${ORG}.
    • The multiOrg roster test ("the SysOrganization rows are every action it gates on multiOrgEnabled") now counts only the rows whose expected gate is MULTI_ORG.
  • sys-organization-name-timezone-help.test.ts: a new case checks that the name help names Rename Organization and that the action carries that label.
  • New packages/qa/dogfood/test/organization-rename-single.dogfood.test.ts. On a booted plain showcase (single), on the wire:
    • The served flags are organization: true, multiOrgEnabled: false. The seeded admin holds an owner or admin grade.
    • The admin is offered Rename Organization, evaluated with the console's celEngine against the served metadata and session positions. The five multi-organization actions stay hidden.
    • A plain member is not offered it. The door refuses the member with 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION, and the name is unchanged.
    • The admin's rename, built the way the console's api runner builds it, returns 200. The name reads back changed and the slug is kept.

Ablation: sys-organization.object.ts, public-auth-features.ts and the four bundles were restored from origin/main in the worktree only, and spec and platform-objects rebuilt.

  • In platform-objects, 4 tests failed and 156 passed: the gate case, the matrix row, the roster test and the new help case.
  • In the dogfood pin, 1 test failed and 3 passed: "the admin is offered Rename Organization". The door itself admits the rename on both trees, because the gate is a console affordance.
  • After restoring HEAD and rebuilding, the dogfood pin passed 4 of 4.

Tests and gates (head d613a6d8, base 49653f91)

  • pnpm --filter @objectstack/spec test: Test Files 716 passed (716), Tests 21725 passed | 1 todo.
  • pnpm --filter @objectstack/platform-objects test: Test Files 94 passed (94), Tests 1344 passed (1344). This run includes feature-gate-guard.test.ts.
  • organization-rename-single, organization-rename-door, org-record-admin-reach and org-change-slug-bound-action (dogfood): Test Files 4 passed (4), Tests 18 passed (18).
  • Typecheck for spec, platform-objects and dogfood: exit 0. eslint on the changed files: clean.
  • dispatch-gates --commands: 91 families. All 91 exited 0, and --ran reports 91 run, 0 NOT-MEASURED.
  • check-i18n-bundles, check:i18n-stale-fill, and check-adr-0087-registration --base origin/main (no breaking changeset) all passed. git merge-tree onto the latest origin/main is clean.
  • Full dogfood battery: not owed. The title has no ! and Clause-② has no (narrowing) arm.

Overlap

Two open PRs touch neighbouring lines, and neither asserts anything about this action:

Whichever lands second needs only a text merge.

Acceptance notes

None new. The first PR's notes still stand: there are two built-in product names (OS_PRODUCT_NAME and OS_APP_NAME), and sys_business_unit.timezone's help promises an inheritance that no code implements.

Changesets: .changeset/25347-rename-organization-single.md (@objectstack/platform-objects patch) and .changeset/25347-rename-organization-auth-feature.md (@objectstack/spec patch).


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01JJZRJcjTF4oBQK7333oAnR

…ame its organization

Rename Organization (name and logo, never the slug) moves its gate from
multiOrgEnabled to the organization capability, and PUBLIC_AUTH_FEATURES
books its row under `organization` in the same change. Under `single` it
was the one organization's only possible name door and it was hidden.
Edit Organization, Create, Delete, Switch and Leave stay multi-org only.

The name field's help names the door in all four bundles. Pins: the
action gate (with update_organization as control), the gate-matrix row,
the multiOrg roster test (now filters rows by gate), and a single-boot
dogfood pin that offers, refuses a plain member, and renames.

Maintainer ruling 6384629949.

Claude-Session: https://claude.ai/code/session_01JJZRJcjTF4oBQK7333oAnR
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/cli.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/seed-tenancy-repair.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/administrator-guide.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/sso.mdx (via multiOrgEnabled (symbol, a field of const object PUBLIC_AUTH_FEATURES), multiOrgEnabled (literal, a string literal in actions))
  • content/docs/protocol/kernel/config-resolution.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/protocol/objectql/schema.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-1.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-4.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-5.mdx (via sys_organization (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json baae6ed14e90e357b6e437e62aed01f0f1be9649 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 92ffe4401dbdd7c59da1a8b16e69cd727632cadb — the merge of head d613a6d86bb2c1bf9b03da51ef531e0d7d1bd9e8 into base baae6ed14e90e357b6e437e62aed01f0f1be9649, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 92ffe4401dbdd7c59da1a8b16e69cd727632cadb && git checkout 92ffe4401dbdd7c59da1a8b16e69cd727632cadb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin baae6ed14e90e357b6e437e62aed01f0f1be9649 d613a6d86bb2c1bf9b03da51ef531e0d7d1bd9e8 && git checkout -B drift-repro baae6ed14e90e357b6e437e62aed01f0f1be9649 && git merge --no-ff d613a6d86bb2c1bf9b03da51ef531e0d7d1bd9e8

node scripts/docs-audit/affected-docs.mjs --json baae6ed14e90e357b6e437e62aed01f0f1be9649

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs baae6ed14e90e357b6e437e62aed01f0f1be9649 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d613a6d86bb2c1bf9b03da51ef531e0d7d1bd9e8
Local-runs: none
Scope: packages/spec/src/kernel/public-auth-features.ts + packages/platform-objects/src/identity/sys-organization.object.ts, packages/platform-objects/src/feature-gate-guard.test.ts, packages/platform-objects/src/identity/sys-organization-rename-action.test.ts, packages/platform-objects/src/platform-objects.test.ts, packages/qa/dogfood/test/organization-rename-single.dogfood.test.ts, packages/plugins/plugin-auth/src/auth-manager.ts (read unchanged, for the served flags), packages/qa/dogfood/test/org-record-admin-reach.dogfood.test.ts (read unchanged, the multi-boot claimant)

Face: schema — reviewFace on the 12-path file list hits the one spec path. The rest of the diff (the two changesets, the four generated bundles, sys-organization-name-timezone-help.test.ts) is the lane seat's ACCEPT at TIER_DEFAULT. Inputs: card #25347 (body and all 7 comments; ruling 6384629949, claim 6384658457, seat pointer 6384842854, os-dev-report 6384984186), PR #25661 (body, file list, net diff against main at the head; merge-base 49653f91, base baae6ed1), and the 40 check-runs on the head (read twice; the second read is the state recorded in ③).

① Derived judgments

  1. The spec row move — right, and exactly the ruling's widening. public-auth-features.ts changes by +5/−3: the row 'sys_organization.actions.rename_organization' leaves multiOrgEnabled.gatedInputs and enters organization.gatedInputs, with its comment. Nothing else moves: PUBLIC_AUTH_FEATURE_NAMES (the requiresFeature enum), both entries' semantics: 'default-on', FEATURE_GATE_OPERATORS and featureGatePredicate are untouched. multiOrgEnabled.gatedInputs still books exactly create_organization, update_organization, delete_organization, set_active_organization and leave_organization; the object declares those five on requiresFeature: 'multiOrgEnabled' (head lines 80, 102, 122, 143, 216), and change_slug stays the cloud composition's.

  2. The gate it mirrors — right. sys-organization.object.ts moves only rename_organization.requiresFeature to 'organization'. requiresMembershipReach: 'update_organization' (owner, admin), the target /api/v1/auth/organization/update, bodyShape and the params (name, logo, no slug) are unchanged. The lowered predicate goes from (ADMINS) && features.multiOrgEnabled != false to (ADMINS) && features.organization != false.

  3. What the move widens, measured against the served flags. plugin-auth's getPublicConfig() serves organization = pluginConfig.organization ?? true and multiOrgEnabled = postureEnforcesWall(tenancyPosture). So under single with the organization plugin on, the door is now offered where it was hidden — the one widening ruling 6384629949 allows. Under group and isolated both flags are true before and after, so nothing changes. With the organization plugin off, the door is now hidden where the old gate would have shown it under a wall posture — a configuration in which the organization/* endpoints do not exist, so this is the same capability gate every other organization input already carries, not a boundary change. No other input's gate moves.

  4. No access boundary is loosened. The feature gate is a console affordance (a lowered visible predicate); the server door is better-auth's organization/update, which was never posture-gated. The PR's ablation records the door admitting the rename on both trees, and the dogfood pin records a plain member refused 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION. Grades are unchanged. rename_organization was not booked under cloudManaged before and is not now; the slug door is untouched.

  5. The guard that binds the row and the gate — satisfied in both directions. feature-gate-guard.test.ts forward: the organization row resolves to the action and its predicate carries features.organization != false as a top-level conjunct. Reverse: the action's only features.* reference is organization, booked there; no stale multiOrgEnabled booking remains. The floor of 37 referenced inputs is unchanged by a move. It runs in the Test Core shards (in progress at read time, see ③).

  6. Pins — moved with the gate, no weakening. The action pin now asserts (reach) && features.organization != false with update_organization on multiOrgEnabled as the control; the matrix row is ${ADMINS} && ${ORG}; the multiOrg roster test keeps its gated side (every action whose predicate names features.multiOrgEnabled) and filters its pinned side to the MULTI_ORG rows, so the five multi-org actions are still pinned as the whole multi-org surface. Other claimants read at the head and unaffected: org-record-admin-reach.dogfood.test.ts (multi boot, organization: true there, so the rename still shows for owner and admin and the grade still hides it from a member), org-change-slug-bound-action (header order unchanged), organization-rename-door, confirm-question-carryover.test.ts. No ADR anchor names either file; no checked-in spec artifact lists the rows (the only match outside src/ is a TS2339 debt entry in test-typecheck-debt.json); no hand-written doc ties the rename to multi-org.

  7. Public TS surface. PUBLIC_AUTH_FEATURES is a published const: the literal type of organization.gatedInputs gains a member and multiOrgEnabled.gatedInputs loses one. No export is added, removed or renamed, and nothing an author or a consumer sends is newly refused, so this is not an accept-set narrowing; check:api-surface is the mechanical check, and its job, Type Check · consumer gates, completed green on this head.

② Semver level

Two changesets, @objectstack/spec patch and @objectstack/platform-objects patch, each carrying Clause-②: yes and Metadata-protocol: none; the PR body carries the same two lines. Right. Metadata-protocol: none holds: the requiresFeature enum is unchanged, and no key, value or type an author writes is added, refused or re-meant; the registry rows are not authorable. The level follows the kind alone (ruling A on #25105; AGENTS.md Post-Task 3): none takes patch, and the widening is non-protocol, so it ships patch. Clause-②: yes with no arm is a well-formed widening in clause2-line.mjs (the arm is optional; the file's older arm docblock still says a widening takes minor, superseded by its own header and AGENTS.md — not this PR's text). No (narrowing) arm, no BREAKING banner, no ADR-0087 disposition owed; Check Changeset is green.

③ Boundary flags

  • os-dev-report 6384984186: open_questions: [], out_of_scope_findings: [] — no dev flag to answer. The one open question of the earlier report 6368023747 (the cross-lane spec row) is what ruling 6384629949 decided and this diff lands.
  • Ruling conditions 1–6: (1) the PR body's "Outside the claim's file surface" section cites 6384629949 — met. (2) the declaration on spec seat post [PM seat] domain:spec — 🟢 os-tesla · session_013odhA26dvzy7pQmevdTTvy #6017 — the report's api_writes records it; [PM seat] domain:spec — 🟢 os-tesla · session_013odhA26dvzy7pQmevdTTvy #6017 is outside this brief's inputs, so it is not verified here: the owning seat confirms it before queueing. (3) this record. (4) mode:cloud — declared in claim 6384658457. (5) the action pin, the matrix row and the multiOrg roster test move with the gate — verified in the diff. (6) organization-rename-single.dogfood.test.ts on a plain showcase boot — read; its four cases cover the served flags, the console-side predicate for admin and member, the member's 403 and the admin's 200 with the slug kept. It ran locally only: the CI dogfood shards skip on pull_request and merge_group by design (ci.yml:2128), so its first CI run is post-merge.
  • Check-runs on the head (second read, 2026-10-24T13:58Z): 40 runs — 26 success, 7 skipped, 7 in progress: Test Core 1–6 (where feature-gate-guard.test.ts and the spec and platform-objects suites run) and Type Check · workspace. Completed green since the first read: Build Core, Type Check · source gates / consumer gates / debt ledger, Lint · CI, release & engine gates, Lint · docs & code guards, and the two scoped lints. None completed red. This record judges the diff; the queue guard still requires every check green, so the seven in-progress runs gate the landing mechanically and this record does not stand in for them.
  • Author: objectstack-fleet[bot]; head repo is the base repo (not a fork); the PR is a draft with auto-merge off.
  • Overlap: feat(plugin-auth): multi-org Create User joins an organization in the same step, defaulting to the admin's current one #25626 and feat(spec,lint): an action's confirmText, successMessage and errorMessage are template slots over the roots the console runner binds #25637 touch neighbouring lines and assert nothing about this action; No other open PR may claim the same single-writer path is green.
  • Noted, not blocking: the new name help names a door only owners and admins are offered, so a plain member reads a pointer to a button they do not see. UX copy, not a contract matter.

Implemented-by: claude/project-thread-xvfy21
Reviewed-by: session_01JrXUZYyTV4i588KLZRxSvJ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T14:25Z

domain:server seat 1 (seat post #6367) · os-sales · session_01JrXUZYyTV4i588KLZRxSvJ · card #25347, second PR under ruling A 6384629949 (claim 6384658457, os-dev-report 6384984186) · reviewed head d613a6d86b · merge base 49653f91


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants