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
`api-surface/` now records the value half of a name declared as both a const and a type, so deleting it is a breaking change the gate reports.
6
+
7
+
TypeScript merges an `export const X` and an `export type X` into ONE symbol whose flags carry both. `build-api-surface.ts` mapped that symbol through a first-match-wins lookup that tested `TypeAlias` before `Variable`, so the shard recorded `X (type)` alone and the value half was never enumerated. Ablated: deleting `export const RestApiRouteRegistration` while keeping its type alias left all 17 shards byte-identical, the export total unmoved, and `check:api-surface` printing "public API surface + factory signatures unchanged" at exit 0 — on a removed public value export, which is the exact removal the ADR-0059 breadth gate exists to make loud. On the fixed generator the same deletion reports `- RestApiRouteRegistration (const)` as 1 breaking change and exits 1.
8
+
9
+
The generator now emits one row per DECLARED kind. The shipped `api-surface/` shards gain **134 rows across 10 of 17 entry points** — every one of them the previously-missing `(const)` half of a name that also declares a type — as a pure insertion: zero rows removed, zero modified, no reordering.
10
+
11
+
**Why `patch` and not `minor`, measured against what a consumer can observe.** No export was added, removed or renamed: `dist/` is byte-identical across this change, and the row grammar `Name (kind)` is untouched, so anything that parsed the artifact before parses it now. The 134 new rows describe exports that already existed — the record got more complete, no capability arrived. What changes is the accuracy of a shipped record and the strictness of this repo's own gate, which is a fix.
12
+
13
+
One consequence for the release seat, stated because it is not visible from the diff: `build-spec-changes.ts --previous-surface` is a release-time join, so a release crossing this change will list those 134 rows as `added` surface entries. They are not new API — they are the same exports, newly recorded.
Copy file name to clipboardExpand all lines: docs/adr/0093-tenancy-mode-and-membership-lifecycle.md
+14-8Lines changed: 14 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@
4
4
-**Date:** 2026-07-13
5
5
-**Deciders:** ObjectStack Protocol Architects
6
6
-**Implementation:**#2882 (Phase 0 — tactical create-user bind, merged) → this PR (Phases 1–3 — `tenancy` service, fail-fast boot guard, membership reconciler, consumer migration, backfill, docs). One revision from the original plan, ratified in D2: the endpoint-level create-user bind **delegates to the shared reconciler** (one implementation, two call sites) instead of being deleted. Runtime verification confirmed the hook fires for `admin.createUser`, but better-auth *defers*`user.create.after` post-commit (#1881), so the endpoint keeps its delegated call to report `organizationId` / `membershipCreated` deterministically in its response. Cloud-host semantics (personal-org hook precedence, multi-org non-binding, D5 blast radius) verified against `objectstack-ai/cloud` — see D2/D3/D5.
7
-
-**Relates to:**[ADR-0049](./0049-no-unenforced-security-properties.md) (no unenforced security properties), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (org-scoped identity optionality), [ADR-0068](./0068-unified-user-context-and-built-in-identity-roles.md) (platform-admin gate), [ADR-0092](./0092-sys-user-profile-field-delegation.md) (identity write guard), the default-org bootstrap (`plugin-auth/src/ensure-default-organization.ts`, referenced in code as "ADR-0081 D1" — that decision record predates this repo's ADR series), #2766 (admin user management), PR #2882 (single-org create-user membership bind — the tactical fix this ADR generalizes)
7
+
-**Relates to:**[ADR-0049](./0049-no-unenforced-security-properties.md) (no unenforced security properties), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (org-scoped identity optionality), [ADR-0068](./0068-unified-user-context-and-built-in-identity-roles.md) (platform-admin gate), [ADR-0092](./0092-sys-user-profile-field-delegation.md) (identity write guard), the default-org bootstrap (`plugin-auth/src/ensure-default-organization.ts`, referenced in code as "cloud ADR-0081 D1" — identified 2026-09-04: that record is **cloud ADR-0081**, *Organization Management — Open Basics, Enterprise `@objectstack/organizations`*, Accepted 2026-07-09 in the `objectstack-ai/cloud`repo's own ADR series, where its **D1** is this bootstrap; it predates *this* repo's series because it lives in that one, where it is current), #2766 (admin user management), PR #2882 (single-org create-user membership bind — the tactical fix this ADR generalizes)
8
8
9
9
## TL;DR
10
10
@@ -65,8 +65,9 @@ Decision:
65
65
resolves a session's `activeOrganizationId` from the caller's `sys_member`
66
66
row (owner-preferred, else oldest), only when the draft lacks one,
67
67
best-effort, opt-out via `autoActiveOrganization: false`. Shipped behaviour,
68
-
previously cited in code as a pre-repo "ADR-0081 D1" whose number now
69
-
collides with this repo's ADR-0081; anchored here because it reads exactly
68
+
previously cited in code as an unqualified "ADR-0081 D1" — the record is
69
+
cloud ADR-0081 (identified 2026-09-04), and written without that qualifier
70
+
the number collides with this repo's ADR-0081; anchored here because it reads exactly
70
71
the invariant D1 states and D2 owns.
71
72
72
73
## Context
@@ -384,7 +385,9 @@ admin surfaces.
384
385
different, larger decision — refused here to keep this ADR mechanical.
385
386
2.**The dual frontend flags keep their meaning.**`features.organization`
386
387
(member management available) vs `features.multiOrgEnabled` (org management
387
-
available) is a deliberate ADR-0081-D1 distinction; only their *backing
388
+
available) is a deliberate cloud ADR-0081 D1/D2 distinction — D1 keeps
389
+
basic member management OPEN, D2 closes multi-org into the enterprise
390
+
`@objectstack/organizations` package; only their *backing
388
391
fact* moves to the tenancy service.
389
392
3.**better-auth keeps owning `sys_member` CRUD.** The reconciler writes
390
393
through the system context exactly as `ensureDefaultOrganization` does; no
@@ -398,12 +401,15 @@ admin surfaces.
398
401
> **This decision is a RECORDING, not a new ruling.** The behaviour below has
399
402
> shipped since before this ADR, and nothing about it changes. What changes is
400
403
> that it now has an anchor. The code carried it as **"ADR-0081 D1"**, a label
401
-
> inherited from a decision record that predates this repo's ADR series (the
402
-
> same pre-repo record the *Relates to* line names for the default-org
403
-
> bootstrap). That number now collides with this repo's
404
+
> inherited from **cloud ADR-0081** — *Organization Management — Open Basics,
405
+
> Enterprise `@objectstack/organizations`*, Accepted 2026-07-09 in the
406
+
> `objectstack-ai/cloud` repo's ADR series (identified 2026-09-04; the same
407
+
> record the *Relates to* line names for the default-org bootstrap). Written
408
+
> without that qualifier the number collides with this repo's
404
409
> [ADR-0081](./0081-trusted-react-page-tier.md) — the trusted `kind:'react'`
405
410
> page tier — so a reader following the citation landed in a document about
406
-
> React pages with no signal they were in the wrong record. The decision is
411
+
> React pages with no signal they were in the wrong record; the surviving
412
+
> citations elsewhere in this repo now carry the `cloud ` qualifier. The decision is
407
413
> restated here because this is the record that owns the fact it depends on:
408
414
> `sys_member` and the membership lifecycle (D1/D2).
Copy file name to clipboardExpand all lines: docs/qa/platform-checklist/areas/identity-auth.json
+5-5Lines changed: 5 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -754,12 +754,12 @@
754
754
"fixtures": {
755
755
"app": "showcase",
756
756
"requires": [
757
-
"the organization capability mounted (always mounted per ADR-0081 D1; plugin-auth's default-org bootstrap guarantees an org to manage) and a session with an active organization so {current_org_id} resolves",
757
+
"the organization capability mounted (always mounted per cloud ADR-0081 D1; plugin-auth's default-org bootstrap guarantees an org to manage) and a session with an active organization so {current_org_id} resolves",
758
758
"at least two members in the org so role changes and removals have a target that is not the admin"
759
759
]
760
760
},
761
761
"steps": [
762
-
"sign in as the org owner/admin and open Setup → People & Org → Organization (nav_organization: type object, objectName sys_organization, recordId {current_org_id}, ADR-0081); screenshot the org record page and confirm {current_org_id} resolved to the session's active org (not the list fallback)",
762
+
"sign in as the org owner/admin and open Setup → People & Org → Organization (nav_organization: type object, objectName sys_organization, recordId {current_org_id}, cloud ADR-0081 D3); screenshot the org record page and confirm {current_org_id} resolved to the session's active org (not the list fallback)",
763
763
"confirm the record page exposes the Members / Invitations / Teams tabs with the better-auth row actions (GET list-members, list-invitations, list-teams feed them)",
764
764
"change a member's role: POST /api/v1/auth/organization/update-member-role (client organizations.updateMemberRole) to one of the ADR-0108 closed 4-name vocabulary {owner, admin, delegated_admin, member}; read the membership back and confirm the new role",
765
765
"prove the vocabulary is CLOSED, not merely conventional: attempt the same call with role 'guest' (and with any stack-declared position/permission-set name, e.g. showcase's 'contributor') — better-auth's role check refuses it (400 ROLE_NOT_FOUND) before any insert, and no membership/invitation row is left behind",
@@ -771,7 +771,7 @@
771
771
],
772
772
"acceptance": [
773
773
{
774
-
"clause": "{current_org_id} resolves to the session's active organization: the Organization nav opens that org's record page (Members/Invitations/Teams tabs), not the raw sys_organization list — the ADR-0081 active-org token is wired",
774
+
"clause": "{current_org_id} resolves to the session's active organization: the Organization nav opens that org's record page (Members/Invitations/Teams tabs), not the raw sys_organization list — the cloud ADR-0081 D3 active-org token is wired",
775
775
"oracle": "screenshot",
776
776
"verify": "the org record page renders for the active org id (cross-checked against GET /api/v1/auth/organization/get-active-member / get-full-organization); the three management tabs are present",
777
777
"evidence": "the org-page screenshot + the get-active/get-full response"
@@ -817,11 +817,11 @@
817
817
"an org management surface where the affordance is hidden but the forged endpoint succeeds for a non-admin is a FAIL — the server is the authority (ADR-0124 D1)",
818
818
"remove-member that drops the roster row but leaves the ex-member's org-scoped access intact is a FAIL — removal must change authorization",
819
819
"a role written outside the {owner, admin, delegated_admin, member} vocabulary is a FAIL — including a stored 'guest': the closed list is the write-side guardrail that makes an ungoverned capability grant unrepresentable (ADR-0108), so a 2xx that persists 'guest' is a regression of the closure, not a vocabulary difference. A role change that does not flip any gate is equally a FAIL",
820
-
"the Organization nav landing on the raw sys_organization list because {current_org_id} did not resolve (when an active org exists) is a FAIL of the ADR-0081 wiring"
820
+
"the Organization nav landing on the raw sys_organization list because {current_org_id} did not resolve (when an active org exists) is a FAIL of the cloud ADR-0081 D3 wiring"
"packages/spec/src/identity/membership-role.ts#BUILTIN_MEMBERSHIP_ROLES (BUILTIN_MEMBERSHIP_ROLES / BUILTIN_MEMBERSHIP_ROLE_OPTIONS — THE role vocabulary: owner/admin/delegated_admin/member, ADR-0108; 'nothing widens these at boot any more')",
827
827
"docs/adr/0108-membership-grade-is-not-a-capability-channel.md (why the list is closed: a grade decides what you can REACH, never a bundle of what you may do)",
0 commit comments