Add atomic owned organization creation
verify / verify (push) Successful in 3m48s

This commit is contained in:
2026-09-04 23:19:05 -04:00
parent b1710e08b8
commit ed0cc8ceff
11 changed files with 586 additions and 18 deletions
+10
View File
@@ -8,6 +8,16 @@ application concern belongs in the shared module.
## Gamertan accounts and commerce
- Shared business purchasing exposed the difference between an initial member
and an initial RBAC owner. The historical organization creation method commits
membership but no access binding. The new `CreateOwnedOrganization` extension
grants the application-configured role and writes both audits atomically for
an existing active, fully registered user. It rejects missing roles and
unsupported adapters instead of leaving an ownerless organization behind.
SQLite tests inject failure at every write stage, including the second audit,
and race duplicate creates. Customer/merchant vocabulary remains application
policy; there is no new database schema or commerce dependency in Foundations.
- The account email remains required and unique. Gamertan uses normalized
email as the canonical login identifier and keeps username as a stable public
identity. Until a mail package exists, the application must not describe an
+1 -1
View File
@@ -26,7 +26,7 @@ The packages are ordinary Go imports. Pin the current preview and verify its
module checksum:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.21
go get gamertan.com/web/requestmeta@v0.1.0-preview.22
go mod verify
```
+1 -1
View File
@@ -18,7 +18,7 @@ import "gamertan.com/web/requestmeta"
and request the containing module at an exact version:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.21
go get gamertan.com/web/requestmeta@v0.1.0-preview.22
```
Only imported packages are compiled and linked. The packages nevertheless
+36
View File
@@ -19,6 +19,42 @@ ordinary invitations but cannot create or cancel owner access.
Applications own invitation pages, email or out-of-band delivery, active-source
checks before archival, and account recovery.
## Creating an organization with an owner
For an existing authenticated user creating a business, use
`CreateOwnedOrganization` with `OwnerRole` configured when constructing the
service. Seed that role first. This commits the organization, active membership,
direct organization-wide owner binding, and both creation/access audit events in
one transaction. `CreateOrganization.RequestID` correlates those audit events.
The owner must be an active user whose registration has completed.
The application authorizes creation and chooses the role; do not accept an owner
role name from a browser or API payload. A customer-owner role can intentionally
have different permissions from an installation's merchant-owner role. Creating
a customer organization grants no authority in any other organization.
```go
customers, err := organizations.New(store, organizations.Options{
OwnerRole: "customer.owner", // Application-defined, already seeded.
})
if err != nil {
return err
}
business, err := customers.CreateOwnedOrganization(ctx, organizations.CreateOrganization{
Slug: "example-business", Name: "Example Business", OwnerUserID: principal.User.ID,
RequestID: requestID,
})
```
Repositories implement `OwnedOrganizationRepository` to support this operation.
There is no create-then-grant fallback: unsupported adapters return
`ErrOwnedCreationUnsupported`. The older `CreateOrganization` and
`CreatePersonalOrganization` retain their membership-only behavior; configuring
`OwnerRole` does not silently change them. The separate `account` package still
owns atomic public signup, including personal organization and credentials.
## Membership and access lifecycle
Organizations and teams use optimistic revisions and reversible
`active`/`archived` states. Archived objects keep their history but contribute
no effective authority. Memberships may be suspended, reactivated, or removed;