Help us shape the future of Go! Take the 2026 Go Developer Survey

organization

package module
v0.0.0-...-f7d9a1a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 23 Imported by: 0

README

go-organization

Status: experimental. Explicit-organization membership and the selected existing-recipient invitation, opt-in team-membership and domain-reservation profiles are implemented. This is not a stable release or complete organization unit.

go-organization supplies storage-neutral organization policy and a native PostgreSQL adapter. The selected profile creates organizations, updates bounded names/slugs, admits existing active users, updates static membership roles, lists members and removes members while protecting the last owner.

API and selection

LifecycleConfig selects native owner archive/restore. Current owners supply a fresh action-bound password proof and exact discovered organization version. Archive denies native child authority and cancels pending invitations atomically; restore never revives terminal child state. Hard deletion and complete asynchronous external cascades remain unselected.

DynamicRoleConfig selects bounded custom roles. Current owners/administrators create/read/list/update/delete roles and assign or revoke them for current members. CheckPermission evaluates exact registered action/resource statements through the existing authorization RBAC engine, with explicit deny precedence and a required current organization policy version. Custom roles confer no organization-management or ownership authority. Zero configuration disables this profile. TeamBindingsVersion: 1 additionally selects team bindings with terminal team lifecycle and its forward migration; zero keeps the existing direct-only profile.

GetDynamicRole optionally accepts TargetUserID to return that exact member's current membership and absent/live/tombstone role binding. Fresh managers can discover the versions needed to revoke or reassign without application history; ListDynamicRoles does not accept a target selector. The team profile instead accepts exclusive TargetTeamID for exact team/binding versions, then PrepareAssignTeamDynamicRole or PrepareRevokeTeamDynamicRole.

DomainConfig selects authenticated domain reservation custody: current owners/administrators prepare a reservation, read their organization's current claim, and withdraw its exact version. Zero leaves this profile disabled. Reservations are globally exclusive in the native PostgreSQL adapter but are never proof of domain ownership and never enable SSO routing.

InvitationService.PrepareResend selects a current owner/administrator and the exact pending invitation and organization versions. It preserves the original recipient, verified identifier, member role and exclusive deadline; it does not accept a replacement address or a renewed lifetime. Preparation alone issues no proof. Durable supersession requires the native capability/delivery owners and one atomic commit, so the predecessor and successor are never independently consumable. Retain the original command for retries and recovery.

Invitation.IssuedVersion identifies the pending proof generation. A pending or naturally expired projection has Version == IssuedVersion; a durable terminal transition has Version == IssuedVersion + 1. For source compatibility, zero IssuedVersion means the original issued version1. Resend requires room for both the successor version and its later terminal transition.

InvitationService.PrepareExpire selects the current owner/administrator and an exact pending invitation/version with a reason from configured InvitationConfig.ExpirationReasons. An empty registry leaves expiry disabled. Explicit expiry can precede the natural deadline or terminalize an elapsed pending row: InvitationExpired at its issued version is only a natural projection; the next version is a durable terminal effect that prevents every prior proof from authorizing acceptance. See the adapter's schema and recovery requirements.

InvitationService.PrepareReject admits only the authenticated intended verified recipient of an exact pending invitation, using original organization/invitation versions without membership or an invitation proof. The native adapter atomically stores the next terminal version with canonical audit/event effects; the original capability can no longer authorize acceptance. See the adapter's migration and recovery requirements.

Use the native postgres.Service for durable operations. The root Service owns real session authentication, external policy, validation and immutable command preparation. Its Repository and exact UnitOfWork.Execute/Recover contracts are adapter boundaries, not application workflow instructions.

  • PrepareCreate selects a preallocated canonical ID and tenant-unique lowercase ASCII slug. The creator becomes an owner atomically.
  • PrepareUpdate replaces only Name (1–256 UTF-8 bytes, no ASCII control characters or surrounding whitespace) and Slug (1–63 lowercase ASCII letters/digits/ hyphens, no leading/trailing hyphen), preserving exact spelling. It requires a current owner or administrator and the expected organization version. Identical values return ErrConflict without a transition. Slug collisions conflict within the same tenant, including reserved lifecycle states.
  • PrepareAddMember requires an existing active target user, static role and expected organization/member versions. Administrators cannot add owners.
  • PrepareUpdateMember changes an exact active membership's static role with expected organization/member versions. Only owners may grant or demote owners; the final recovery-capable owner cannot be demoted. See static role updates for authority and recovery.
  • PrepareRemoveMember requires exact versions and retains a tombstone. Only owners remove owners; the final owner cannot be removed.
  • PrepareLeaveMember accepts an explicit organization/version and derives self from the current session, including ordinary members. It cannot select another target or remove the final recovery-capable owner. See self leave.
  • PrepareOwnerTransfer selects an existing active nonowner member and exact organization/source/target versions. A current owner and operation-bound primary proof are required; the former owner becomes an ordinary member. See ownership transfer for authority and recovery.
  • Execute accepts the same immutable prepared command for bounded retries.
  • Get and ListMembers require current active membership on every call. Listing uses exclusive UserID keysets and limits 1–100 over current committed state, not a retained snapshot. Earlier concurrent additions require restart.
  • ListOrganizations lists only the authenticated human's active organizations with 1–200 results, per-organization policy and stable visible-ID pagination. Its additive repository capability leaves existing implementations compatible. See organization listing for filtering and bounds.
  • Recover authenticates the current actor against exact original scope. An ID alone grants no result access; recovery never re-executes a command.

Membership removal advances scoped organization authorization, not unrelated global user or credential authority. Re-admission advances the retained membership and scoped authority rather than resetting them. Metadata updates advance only the existing organization/policy epoch; they do not change memberships, scoped subject authority or global identity authority. Read the updated organization through authenticated Get after the receipt.

The selected update profile maps the planning operation identity.organization.update onto the existing Authentication, stable CommandID, OrganizationVersion, and PrepareUpdate/Execute/Get model. It retains the committed canonical-ID policy and create-name 256-byte bound, not older generated catalog assumptions of 22-character IDs and 128-byte names. Optional typed additional fields and the one-call/HTTP UpdateResult surface are unselected; they are never silently cleared or emulated.

Non-goals

The root boundary does not own:

  • enterprise single sign-on protocol behavior or SCIM wire behavior;
  • billing or user interfaces;
  • session persistence or application authorization policy; or
  • database clients, migrations, transactions, caches, or background workers.

Domain challenges and ownership proofs, hard-delete commands, active selection, cache invalidation, SSO/SCIM and HTTP/browser transport are unselected. Reserved database states are not public operations. Their presence in planning material does not make them available.

Selected invitation boundary

The storage-neutral InvitationService adds owner-send, intended-recipient acceptance/rejection and owner/administrator resend/cancellation/expiry preparation to the same immutable Command/UnitOfWork contract. This root boundary alone is not durable invitation issuance or consumption; the native postgres.NewInvitationService supplies the sole coordinator, capability and delivery owners from actual public configuration. Applications do not implement a token store or transaction coordinator.

Send selects an existing active user and exact verified email identifier. It freezes the organization, recipient, identifier/version, member-only role and exclusive expiry; it never accepts a caller-selected address or privileged role. Acceptance requires independent genuine session authentication and an exact one-use, non-bearer capability for that recipient, invitation and organization. Existing active membership conflicts without overwriting its role or consuming the capability. Current recipient, policy, expiry and membership must be fenced again by native execution, atomically with shared capability consumption.

Get/List expose only current owner-authorized scalar metadata and the existing delivery owner's state/effect ID. They expose no transport address, proof or encrypted custody. Provider Accepted is not confirmed Delivered. Pending records project as expired at the exclusive deadline without a new mutation. Listing uses exclusive invitation-ID keysets with limits1–100. Prospective-user signup and grants above member are not selected by this profile.

Configuration explicitly selects a lifetime within15 minutes–30 days and a pending bound within1–10000; the reference selection is48 hours/100. A48-hour composition must explicitly enlarge capability lifetime limits and delivery deduplication retention to at least lifetime+24 hours. No default capability policy is silently weakened. Neither verification nor metadata is permission to consume, and unknown command outcomes retain the original invitation ID.

Native send/accept and new-service metadata recovery are exercised by the genuine public consumer. A definitive failed acceptance terminal-releases its unused grant: it is not reusable under a new command. A new owner-authorized send can issue a distinct grant within the pending limit; it does not silently resend or cancel the old pending row. Preserve the safe recovery handle before executing and reconcile ambiguous outcomes before selecting any new command.

The owning lifecycle catalog adds invitation.created.v1 (invitation0→1) and invitation.accepted.v1 (issued invitation version→next terminal version). Acceptance also requires the distinct existing membership.created.v1 transition for its actual membership version; an invitation event must not masquerade as that membership event. Exact audit actions are identity.organization.create_invitation and identity.organization.accept_invitation.

PrepareCancel(InvitationCancelRequest) selects the exact live pending invitation and expected organization/invitation versions. It authenticates a current owner or administrator, not the recipient, and needs no invitation proof or current recipient email. The distinct callable operation is identity.organization.invitation.cancel; its canonical audit action is identity.organization.cancel_invitation (standard retention), paired with invitation.cancelled.v1 (invitation1→2). Existing send/accept retention is unchanged. Cancellation grants no membership and changes no organization or global identity authority. Cancelled IDs remain terminal: a still cryptographically valid predecessor proof, including an already-prepared acceptance, cannot pass the shared pending/version fence. Retries preserve the original command; metadata recovery requires current management authority.

ListMine(InvitationListMineRequest) is a distinct current-recipient read, without organization membership or administrator permission. It returns only safe Invitation values bound to the authenticated human and an exact current verified email identifier ID/version. Every supported lifecycle state is visible; pending records project expired at their exclusive deadline. Old identifier versions, removed or unverified identifiers are not historical access grants. No delivery status, address, capability or ciphertext is returned.

The optional InvitationListMineReader preserves existing repository callers. Pages use limits1–200 and a scope-bound opaque continuation, with tenant-unique invitation IDs in C byte order. This is a current keyset, not a snapshot: earlier inserts require restarting. No page escapes on late identity/session/policy or invitation withdrawal; original session freshness is never renewed. Retained invitation metadata itself grants neither membership nor acceptance authority.

Lifecycle and ownership

Supply a real native stateful opaque bearer session with verified single-factor password authentication. Organization-, factor- and passkey-bound sessions are rejected, not downgraded. Supply external policy, canonical-ID validation, positive operation budgets of at most five seconds, and finite member/per-user/per-tenant limits. Native budgets must fit the enclosing budget.

The caller owns primary pools, transport, retained keys and shutdown. Mutable key maps are copied; no package background work or authorization cache starts. Do not log names or bearer-bearing preparations. Commands and authentication reject automatic serialization; persist only safe recovery scope.

Outcomes and retries

Inspect StoreResult.Receipt.Outcome, not error presence alone. Terminal NotCommitted replay may return a nil error. Unknown means the server may have committed: reconcile through scoped recovery, never a new command ID. Preparing again captures new observations and is not an idempotent replay. Retries and recovery never renew original session expiry.

FAQ and tradeoffs

Why explicit IDs? Active-session organization selection is a later profile; current membership and policy are checked for the supplied ID.

Why a tenant lock? The native adapter serializes tenant mutations to protect cardinality and last-owner invariants. This profile makes no throughput claim.

Planning and verification

The repository goal describes the full later unit, not current completion. modules.json records the experimental root and optional adapter. Full platform composition and release qualification remain pending.

See the security boundary, release notes, and SECURITY.md for private reporting; no stable release is supported.

Use Go 1.27.2 and the qualified public Tools v2.1.2 binary; the planned module minimum remains Go 1.27.0. make check, make ci and make fast-check validate configuration, repository structure, cohesion, offline specification decisions, local documentation links and whole-tree/history secrets. CI uses the same structural commands plus validated PR-base or push-before changed-content secret checks on every change. Ordinary Required has a five-minute job limit; changed secrets have a forty-second fatal timeout. Scheduled/manual runs retain whole-tree secrets and online authority refresh. make check-full explicitly preserves the previous full/online contract. These commands do not run application tests or establish runtime/release proof. See contributing for public tooling installation and scope.

See the versioned Golib ecosystem index and package-family guidance for the shared design language.

Optional team-membership profile

Set the same explicit TeamConfig{Version: 1, MaximumTeams: 100, MaximumMembers: 100} on core and native contributor configuration, and install the adapter's forward TeamMigrationSource after existing organization migrations. Zero configuration disables the entire profile and registers no team mutations. Limits are bounded startup policy, not implied protocol defaults. MaximumMembers caps currently compatible active bindings, not lifetime retained removal tombstones; operators retain responsibility for storage/retention sizing.

Owners and administrators call PrepareCreateTeam, PrepareAddTeamMember or PrepareRemoveTeamMember, then the adapter's ordinary Execute; preserve its prepared command/recovery binding for replay or ambiguous commits. Requests bind the exact organization policy, team, organization-member and team-membership versions. Use ListTeams for management, ListMyTeams for the authenticated subject, and ListTeamMembers for managers or current members of that team. Reads are bounded exclusive-ID pages and revalidate native current session, organization membership and scoped authority after action policy.

Each team mutation advances the organization policy epoch. Membership changes also advance that subject's existing organization-scoped authorization epoch, without changing roles, granting permissions or global session authority. Organization removal immediately makes old team bindings unusable; re-admission requires an explicit team add against the new organization-member version and retained team tombstone version. The additional team lifecycle profile selects exact get, versioned name update and terminal delete only after its forward migration and explicit LifecycleVersion: 1. Direct custom roles are selected separately by DynamicRoleConfig; its optional TeamBindingsVersion: 1 adds effective team permissions. Active team selection, HTTP/reference registration and full M23 remain unselected.

License

MIT. See LICENSE.

Documentation navigation

Complete documentation map

Use the versioned ecosystem entry for package selection. The frozen compatibility cohort is separate from current package releases and unpublished source changes. Adopting them does not silently update that cohort.

Documentation

Overview

Package organization owns storage-neutral organization and membership policy. Its selected profiles use an explicitly selected organization and genuine bearer sessions. Invitation preparation selects only existing verified email recipients and member grants. Durable adapters own locking and atomic effects; active-session selection, dynamic team permissions and caches remain unselected.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidInput rejects invalid configuration or bounded request syntax.
	ErrInvalidInput = errors.New("organization: invalid input")
	// ErrForbidden deliberately conflates absence, inactive authority and denial.
	ErrForbidden = errors.New("organization: forbidden")
	// ErrConflict rejects duplicate or stale optimistic transitions.
	ErrConflict = errors.New("organization: conflict")
	// ErrLastOwner denies removal or demotion of the final recovery-capable owner.
	ErrLastOwner = errors.New("organization: last owner")
	// ErrLimit denies a configured organization or membership cardinality limit.
	ErrLimit = errors.New("organization: limit exceeded")
	// ErrUnavailable redacts dependency, invalid-output and panic diagnostics.
	ErrUnavailable = errors.New("organization: unavailable")
	// ErrSensitiveSerialization prevents implicit export of runtime custody.
	ErrSensitiveSerialization = errors.New("organization: sensitive serialization forbidden")
)

Functions

func CanonicalDomain

func CanonicalDomain(input string) (string, error)

CanonicalDomain implements this profile's deterministic ASCII-only name policy: 2 or more LDH labels, 1..63 bytes each, at most 253 canonical bytes, and a final label containing at least one ASCII letter. ASCII case folds and one terminal root dot is removed. Whitespace, wildcard, IP literals, Unicode and implicit IDNA conversion are rejected, not trimmed or interpreted. An ASCII A-label is treated as supplied ASCII; this function makes no IDNA conformance claim.

func ValidateRoleStatements

func ValidateRoleStatements(c DynamicRoleConfig, statements []RoleStatement) error

ValidateRoleStatements rejects unknown or ambiguous statements before storage and again before evaluation. Empty statements intentionally grant nothing.

Types

type Authentication

type Authentication struct {
	TenantID identity.TenantID
	Bearer   authentication.BearerCredential
}

Authentication selects only explicit bearer transport. Cookie/CSRF workflows require the session owner's native browser composition and are not emulated.

func (Authentication) Format

func (Authentication) Format(s fmt.State, _ rune)

func (Authentication) MarshalJSON

func (Authentication) MarshalJSON() ([]byte, error)

func (Authentication) MarshalText

func (Authentication) MarshalText() ([]byte, error)

type Command

type Command struct {
	// contains filtered or unexported fields
}

Command is immutable Service-issued preparation, not a transaction/receipt. Zero is invalid. Only a native adapter's public service composes Execute with its own invocation-scoped UnitOfWork; applications need no workflow glue.

func (Command) Format

func (Command) Format(s fmt.State, _ rune)

func (Command) Input

func (c Command) Input() (CommandInput, error)

func (Command) MarshalJSON

func (Command) MarshalJSON() ([]byte, error)

func (Command) MarshalText

func (Command) MarshalText() ([]byte, error)

type CommandInput

type CommandInput struct {
	Binding                                          identity.CommandBinding
	Operation                                        Operation
	Organization                                     Organization
	Actor, Target                                    Member
	TargetUser                                       identity.User
	Role                                             Role
	Session                                          session.GetResult
	Bearer                                           authentication.BearerCredential
	MaximumMembers, MaximumPerUser, MaximumPerTenant uint64
	Invitation                                       InvitationInput
	Team                                             Team
	TeamMember                                       TeamMember
	Teams                                            TeamConfig
	DomainClaim                                      DomainClaim
	DomainProof                                      DomainProof
	Domains                                          DomainConfig
	DynamicRole                                      DynamicRole
	RoleBinding                                      RoleBinding
	TeamRoleBinding                                  TeamRoleBinding
	DynamicRoles                                     DynamicRoleConfig
	OwnerTransferProof                               authentication.ReauthenticationProof
	Lifecycle                                        LifecycleConfig
	LifecycleState                                   LifecycleState
	LifecycleReason                                  string
	LifecycleProof                                   authentication.ReauthenticationProof
}

CommandInput is the trusted adapter projection of a Service-issued command. It contains original private session admission and bearer custody: do not log, persist or automatically serialize it. Adapters map every field into the sole durable owner's keyed command; none alone grants mutation permission.

func (CommandInput) Format

func (CommandInput) Format(s fmt.State, _ rune)

func (CommandInput) MarshalJSON

func (CommandInput) MarshalJSON() ([]byte, error)

func (CommandInput) MarshalText

func (CommandInput) MarshalText() ([]byte, error)

type Config

type Config struct {
	Clock                                            clock.Clock
	MaximumDuration                                  time.Duration
	PolicyID                                         string
	PolicyVersion                                    uint64
	MaximumMembers, MaximumPerUser, MaximumPerTenant uint64
	ValidateCanonicalID                              func(context.Context, ID) error
	Teams                                            TeamConfig
	Domains                                          DomainConfig
	DynamicRoles                                     DynamicRoleConfig
	Lifecycle                                        LifecycleConfig
	// OwnerTransferAudience explicitly selects the signed primary-proof audience.
	// Empty disables ownership transfer; it is independent of session audience.
	OwnerTransferAudience string
}

Config is explicit immutable policy. Limits are finite and enforced again by the durable adapter under its own locks. MaxMembers follows the reference organization.max_members range; MaxPerUser follows organization.max_per_user. MaximumPerTenant is an additional deployment-selected cap, not a reference default. Canonical validation preserves, rather than transforms, supplied IDs.

type CreateRequest

type CreateRequest struct {
	Authentication            Authentication
	CommandID                 identity.CommandID
	ID                        ID
	Slug, Name, CorrelationID string
}

CreateRequest preallocates the canonical ID and preserves CommandID across retries. Slugs are lowercase ASCII labels of 1..63 bytes, unique per tenant; names are 1..256 UTF-8 bytes without control characters. They are never logs.

type CreateTeamRequest

type CreateTeamRequest struct {
	Authentication         Authentication
	CommandID              identity.CommandID
	OrganizationID, TeamID ID
	OrganizationVersion    uint64
	Name, CorrelationID    string
}

CreateTeamRequest creates a named bounded team without adding members. IDs preserve canonical caller identities and are unique within an organization.

type DeleteTeamRequest

type DeleteTeamRequest struct {
	Authentication                   Authentication
	CommandID                        identity.CommandID
	OrganizationID, TeamID           ID
	OrganizationVersion, TeamVersion uint64
	CorrelationID                    string
}

DeleteTeamRequest permanently retires this team identity at exact epochs. Existing member bindings cease to grant access without per-member fan-out; recreating the ID is forbidden. Historical command recovery remains separate.

type DomainClaim

type DomainClaim struct {
	ID, OrganizationID ID
	TenantID           identity.TenantID
	Domain             string
	Version            uint64
	State              DomainClaimState
}

DomainClaim is a primary custody snapshot bound to an immutable claim identity and exact tenant/organization. Withdrawal advances Version and retains the original Domain and ID forever; a later reservation requires a new ID.

type DomainClaimReadRequest

type DomainClaimReadRequest struct {
	Authentication          Authentication
	OrganizationID, ClaimID ID
	CorrelationID           string
}

DomainClaimReadRequest selects only an own-organization immutable claim ID, including its withdrawn tombstone. It cannot search for a competing owner.

type DomainClaimRequest

type DomainClaimRequest struct {
	Authentication                    Authentication
	CommandID                         identity.CommandID
	OrganizationID, ClaimID           ID
	OrganizationVersion, ClaimVersion uint64
	Domain, CorrelationID             string
}

DomainClaimRequest freezes an exact tenant/org, immutable ClaimID, canonical Domain and expected organization epoch. Reserve requires ClaimVersion zero; withdraw requires the exact current requested version. Preserve CommandID and the original prepared command for replay; never retry Unknown with a new ID.

type DomainClaimState

type DomainClaimState string

DomainClaimState is the closed reservation-only lifecycle. Neither state is domain ownership evidence. Future challenge/proof profiles remain unselected.

const (
	DomainRequested DomainClaimState = "requested"
	DomainWithdrawn DomainClaimState = "withdrawn"
)

type DomainConfig

type DomainConfig struct {
	Version, MaximumClaims uint64
	// EvidenceMaximumAge explicitly enables proof issuance and bounds original
	// observation lifetime. identity-platform/REFERENCE_CONFIGURATION.md's
	// sso.domain_proof.ttl allows 15m..48h;
	// zero preserves reservation-only behavior. Observers may return less time.
	EvidenceMaximumAge time.Duration
}

DomainConfig explicitly selects reservation custody and, independently, an optional bounded evidence lifetime. Zero disables it. Version binds immutable deployed policy; MaximumClaims bounds all retained claims (including withdrawn tombstones) per organization to 1..10,000. There is no expiry or automatic takeover.

func (DomainConfig) Validate

func (c DomainConfig) Validate() error

type DomainEvidenceCommand

type DomainEvidenceCommand struct {
	// contains filtered or unexported fields
}

DomainEvidenceCommand is immutable organization-issued observation custody. Zero is invalid. Persist its safe Recovery binding before Apply; never repeat an external observation to recover an ambiguous outcome.

func (DomainEvidenceCommand) Command

func (c DomainEvidenceCommand) Command() Command

Command exposes the trusted storage-neutral projection for native composition, never another observation or a caller-constructible mutation grant.

func (DomainEvidenceCommand) Format

func (DomainEvidenceCommand) Format(s fmt.State, _ rune)

func (DomainEvidenceCommand) MarshalJSON

func (DomainEvidenceCommand) MarshalJSON() ([]byte, error)

func (DomainEvidenceCommand) MarshalText

func (DomainEvidenceCommand) MarshalText() ([]byte, error)

type DomainEvidenceObserver

type DomainEvidenceObserver interface {
	ObserveDomain(context.Context, DomainClaim) (DomainObservation, error)
}

DomainEvidenceObserver is a deployment-selected external observation seam, called once outside all durable locks. It is not a competing proof authority. DNS/HTTPS adapters must enforce their own bounded challenge verification. Implementations must honor cancellation and support concurrent invocations.

type DomainEvidenceRequest

type DomainEvidenceRequest struct {
	Claim        DomainClaimRequest
	ProofVersion uint64
}

DomainEvidenceRequest selects an existing requested claim and exact current proof version. This finite profile issues once (zero -> one), then optionally terminally revokes (one -> two); renewal/takeover requires a new claim identity.

type DomainEvidenceResult

type DomainEvidenceResult struct {
	StoreResult
	Proof DomainProof
}

DomainEvidenceResult retains the sole identity command outcome. Proof is set only for committed outcomes and is the identical original historical proof.

type DomainEvidenceTransition

type DomainEvidenceTransition interface {
	ApplyDomainEvidence(context.Context, DomainEvidenceCommand) (DomainEvidenceResult, error)
	RecoverDomainEvidence(context.Context, identity.CommandID) (DomainEvidenceResult, error)
}

DomainEvidenceTransition is invocation-scoped authenticated transition custody. Apply attempts an already observed exact command; Recover never observes or reexecutes it. Unknown and not-committed outcomes carry no proof.

type DomainMethod

type DomainMethod string

DomainMethod names the explicitly supported observer profiles. A method name is not verification: only the configured trusted observer may produce evidence.

const (
	DomainDNS   DomainMethod = "dns-txt"
	DomainHTTPS DomainMethod = "https"
)

type DomainObservation

type DomainObservation struct {
	Claim                 DomainClaim
	Method                DomainMethod
	ObservedAt, ExpiresAt time.Time
	Verified              bool
}

DomainObservation is a trusted observer's bounded outcome for the exact claim. The observer, not a request field or method label, must verify the external challenge. Raw challenge material must not be returned, logged or persisted.

type DomainProof

type DomainProof struct {
	TenantID                   identity.TenantID
	OrganizationID, ClaimID    ID
	Domain                     string
	ClaimVersion, ProofVersion uint64
	Method                     DomainMethod
	ObservedAt, ExpiresAt      time.Time
	Revoked                    bool
}

DomainProof is an organization-owned immutable authoritative snapshot, not a bearer grant. Current use requires GetDomainProof; historical command recovery intentionally returns the original snapshot even after expiry or revocation. Times are UTC microseconds (PostgreSQL precision); expiry is exclusive.

type DomainProofKey

type DomainProofKey DomainProof

DomainProofKey binds every authority dimension of an exact proof. Zero is invalid. Construct it by converting an authority-issued DomainProof; changing any field never selects a different authority or renews its freshness.

type DomainProofReader

type DomainProofReader interface {
	GetDomainProof(context.Context, DomainProofKey) (DomainProof, error)
}

DomainProofReader consults primary current authority, never a cached allow.

type DynamicRole

type DynamicRole struct {
	ID, OrganizationID ID
	TenantID           identity.TenantID
	Name               string
	Version            uint64
	Statements         []RoleStatement
}

DynamicRole is a current bounded custom role snapshot, never a static role. Updating replaces all statements immediately for every current binding; deleting terminally retires its ID and denies all of its bindings.

type DynamicRoleConfig

type DynamicRoleConfig struct {
	Version, MaximumRoles uint64
	Catalog               []Permission
	// TeamBindingsVersion1 opts into direct+team effective role evaluation and
	// team-target role.assign/revoke. Zero preserves direct-only command bytes.
	TeamBindingsVersion uint64 `json:",omitempty"`
}

DynamicRoleConfig selects the custom-role profile. Zero disables it. MaximumRoles bounds retained role identities per organization (1..100), including deleted tombstones, so delete/create churn cannot grow storage. Catalog is copied at construction, has 1..128 distinct entries, and is part of durable command identity. Changing it requires a deployment policy epoch. Custom management permissions remain unsupported.

func (DynamicRoleConfig) Fingerprint

func (c DynamicRoleConfig) Fingerprint() string

Fingerprint identifies this complete immutable policy, not a source revision.

func (DynamicRoleConfig) Validate

func (c DynamicRoleConfig) Validate() error

Validate accepts the disabled zero policy or a bounded complete catalog.

type DynamicRoleReadRequest

type DynamicRoleReadRequest struct {
	Authentication         Authentication
	OrganizationID, RoleID ID
	TargetUserID           identity.UserID
	// TargetTeamID selects one current team binding; it is mutually exclusive
	// with TargetUserID and requires TeamBindingsVersion1. List rejects both.
	TargetTeamID  ID
	Cursor        string
	Limit         uint32
	CorrelationID string
}

DynamicRoleReadRequest selects one exact role or an exclusive ID-keyset page. Reads require current static owner/admin authority. List requires Limit 1..100 and empty RoleID/target selectors; Get requires RoleID, zero Limit and empty Cursor. Get's optional TargetUserID returns that exact organization membership and its absent/live/tombstone RoleBinding, including versions for a fresh manager. Removed membership state remains explicit; a stored binding is never authority.

type DynamicRoleRequest

type DynamicRoleRequest struct {
	Authentication                   Authentication
	CommandID                        identity.CommandID
	OrganizationID, RoleID           ID
	OrganizationVersion, RoleVersion uint64
	Name, CorrelationID              string
	Statements                       []RoleStatement
}

DynamicRoleRequest replaces Name/Statements for create/update. Delete accepts neither replacement field and retires all bindings without reassignment. RoleVersion is zero only for create; OrganizationVersion must always match.

type ID

type ID string

ID preserves a canonical organization identifier, including existing ULIDs. It carries neither ownership nor membership authority. IDs are case-sensitive.

type Invitation

type Invitation struct {
	ID, OrganizationID         ID
	TenantID                   identity.TenantID
	RecipientUserID            identity.UserID
	RecipientIdentifierID      identity.IdentifierID
	RecipientIdentifierVersion uint64
	CreatedBy                  identity.UserID
	Role                       Role
	Status                     InvitationStatus
	Version                    uint64
	// IssuedVersion is the last pending version bound to an issued proof.
	// Zero preserves legacy source projections and means first issuance (one).
	// Pending/natural-expired Version equals it; terminal Version is one later.
	IssuedVersion uint64
	ExpiresAt     time.Time
}

Invitation is safe scalar metadata. It contains neither a transport address nor proof/digest/ciphertext. It grants no access and never authenticates its recipient. Versions and IDs are exact tenant/organization scoped values.

type InvitationAcceptRequest

type InvitationAcceptRequest struct {
	Authentication                         Authentication
	CommandID                              identity.CommandID
	ID, OrganizationID                     ID
	OrganizationVersion, InvitationVersion uint64
	Proof                                  capability.Proof
	CorrelationID                          string
}

InvitationAcceptRequest supplies the exact invitation, current organization and invitation versions and privately held capability. Authentication must independently resolve its intended verified recipient; proof is never login.

func (InvitationAcceptRequest) Format

func (InvitationAcceptRequest) Format(s fmt.State, _ rune)

func (InvitationAcceptRequest) MarshalJSON

func (InvitationAcceptRequest) MarshalJSON() ([]byte, error)

func (InvitationAcceptRequest) MarshalText

func (InvitationAcceptRequest) MarshalText() ([]byte, error)

type InvitationCancelRequest

type InvitationCancelRequest struct {
	Authentication                         Authentication
	CommandID                              identity.CommandID
	ID, OrganizationID                     ID
	OrganizationVersion, InvitationVersion uint64
	CorrelationID                          string
}

InvitationCancelRequest selects an exact live pending invitation and both original aggregate versions. The current owner/administrator is authenticated independently; cancellation never needs the recipient's bearer or proof.

type InvitationConfig

type InvitationConfig struct {
	Audience       string
	Lifetime       time.Duration
	MaximumPending uint64
	// ExpirationReasons enables explicit expiry for these registered, non-secret
	// codes only. Empty disables expiry, preserving existing profiles. At most
	// 32 unique 1..64-byte codes are accepted and copied at construction.
	ExpirationReasons []identity.PolicyReason
}

InvitationConfig selects owner-send/member-accept, recipient rejection and administrator cancellation/registered expiry for existing verified email recipients. Lifetime follows reference bounds15m..30d; MaximumPending follows1..10000. Zero is invalid. The composition selects48h/100 explicitly, and must enlarge capability cryptographic lifetime limits to match; the root never weakens the capability owner's defaults or creates signing keys.

type InvitationCursor

type InvitationCursor string

InvitationCursor is an opaque, non-authoritative continuation. Empty starts a page; return a supplied continuation unchanged, never across subjects.

type InvitationDelivery

type InvitationDelivery struct {
	Invitation Invitation
	EffectID   delivery.IntentID
	State      delivery.DeliveryState
}

InvitationDelivery combines safe invitation metadata with the existing delivery owner's state. Accepted is provider acceptance, not Delivered; an ambiguous delivery never rolls back an already committed invitation. A missing effect is represented by empty EffectID and zero State, never by inventing provider failure/success. Command outcomes distinguish no queue commitment from an unresolved commit.

type InvitationExpireRequest

type InvitationExpireRequest struct {
	Authentication                         Authentication
	CommandID                              identity.CommandID
	ID, OrganizationID                     ID
	OrganizationVersion, InvitationVersion uint64
	Reason                                 identity.PolicyReason
	CorrelationID                          string
}

InvitationExpireRequest terminalizes an exact pending invitation. Reason is a registered non-secret code, never free-form text. Independent owner/admin authentication is required; recipient credentials and proof are not authority.

type InvitationInput

type InvitationInput struct {
	Record              Invitation
	RecipientIdentifier identity.Identifier
	MaximumPending      uint64
	Proof               capability.Proof
	ExpirationReason    identity.PolicyReason
}

InvitationInput is trusted immutable command custody for the native adapter. Record is safe metadata; RecipientIdentifier contains sensitive canonical email and must enter only authenticated encrypted native preparation custody, never command safe_fields, audit, invitation rows or metadata responses.

func (InvitationInput) Format

func (InvitationInput) Format(s fmt.State, _ rune)

func (InvitationInput) MarshalJSON

func (InvitationInput) MarshalJSON() ([]byte, error)

func (InvitationInput) MarshalText

func (InvitationInput) MarshalText() ([]byte, error)

type InvitationListMineQuery

type InvitationListMineQuery struct {
	TenantID    identity.TenantID
	RecipientID identity.UserID
	After       ID
	Limit       uint32
}

InvitationListMineQuery is the trusted self scope selected after native session authentication. Read at most Limit+1 rows after the exclusive ID; Limit is 1..200. Tenant+invitation ID is the installed schema's unique key.

type InvitationListMineReader

type InvitationListMineReader interface {
	ReadInvitationsMine(context.Context, InvitationListMineQuery) (InvitationListMineSnapshot, error)
}

InvitationListMineReader is an optional additive repository capability. Existing InvitationRepository implementations need not implement it unless listing is selected. Read current primary state, honor ctx, and release rows before returning; never retain a positive authority cache or external locks.

type InvitationListMineRequest

type InvitationListMineRequest struct {
	Authentication Authentication
	Cursor         InvitationCursor
	Limit          uint32
	CorrelationID  string
}

InvitationListMineRequest binds self through Authentication, never a claimed recipient. Limit is 1..200; Cursor is empty or a returned <=512-byte locator. Current verified identifier ID/version matches are required for every state.

type InvitationListMineResult

type InvitationListMineResult struct {
	Invitations []Invitation
	NextCursor  InvitationCursor
}

InvitationListMineResult owns safe invitation metadata only, without email, capability, delivery custody or permission to accept. All retained installed states are included; live pending rows project Expired at exclusive expiry. Pages use tenant-unique invitation IDs in bytewise C order over current state, not a retained snapshot. Earlier additions require restarting the listing.

type InvitationListMineSnapshot

type InvitationListMineSnapshot struct {
	Recipient                  identity.User
	IdentifierAuthorityVersion uint64
	Identifiers                []InvitationRecipientIdentifier
	Invitations                []Invitation
}

InvitationListMineSnapshot transfers independently owned primary projections. Identifiers contains the complete bounded (<=128) current verified-email ID/version set, sorted by ID, and IdentifierAuthorityVersion is its directory owner's current epoch. No membership is required. Returned invitations must match Recipient and a current identifier exactly; unknown states fail closed.

type InvitationPage

type InvitationPage struct {
	Organization Organization
	Actor        Member
	Invitations  []InvitationDelivery
	NextCursor   ID
}

InvitationPage is a current primary keyset page, not a retained snapshot. Actor and Organization are current policy inputs, never returned grants.

type InvitationQuery

type InvitationQuery struct {
	Operation                    Operation
	TenantID                     identity.TenantID
	ActorID, RecipientUserID     identity.UserID
	OrganizationID, InvitationID ID
	RecipientIdentifierID        identity.IdentifierID
	Cursor                       ID
	Limit                        uint32
}

InvitationQuery is a trusted bounded primary projection selected after real authentication. Send selects a current verified existing email identifier; acceptance does not require the recipient to be a member before mutation.

type InvitationReadRequest

type InvitationReadRequest struct {
	Authentication     Authentication
	OrganizationID, ID ID
	Cursor             ID
	Limit              uint32
	CorrelationID      string
}

InvitationReadRequest selects owner-only safe metadata. Get requires ID and zero pagination; List requires no ID, Limit1..100 and an exclusive invitation ID cursor in bytewise C order. Pages are current, not retained snapshots.

type InvitationRecipientIdentifier

type InvitationRecipientIdentifier struct {
	ID      identity.IdentifierID
	Version uint64
}

InvitationRecipientIdentifier is a current verified email's safe identifier projection. It carries no address or proof and cannot authenticate a subject.

type InvitationRejectRequest

type InvitationRejectRequest struct {
	Authentication                         Authentication
	CommandID                              identity.CommandID
	ID, OrganizationID                     ID
	OrganizationVersion, InvitationVersion uint64
	CorrelationID                          string
}

InvitationRejectRequest selects an exact pending invitation and original versions. Authentication must independently identify its current verified recipient. Rejection needs neither membership nor the invitation bearer.

type InvitationRepository

type InvitationRepository interface {
	ReadInvitation(context.Context, InvitationQuery) (InvitationSnapshot, error)
	ListInvitations(context.Context, InvitationQuery) (InvitationPage, error)
}

InvitationRepository is the storage-neutral trusted preparation/read port. Returned values are independently owned and bounded. It never authenticates sessions or consumes a capability. Implementations must support cancellation and concurrent independent reads without any positive membership cache.

type InvitationResendRequest

type InvitationResendRequest struct {
	Authentication                         Authentication
	CommandID                              identity.CommandID
	ID, OrganizationID                     ID
	OrganizationVersion, InvitationVersion uint64
	CorrelationID                          string
}

InvitationResendRequest selects an exact pending invitation and organization policy epoch. The original recipient, verified identifier, static member role and exclusive deadline are preserved; callers cannot redirect delivery or extend the grant. Retain CommandID and the prepared command for exact retries.

type InvitationSendRequest

type InvitationSendRequest struct {
	Authentication        Authentication
	CommandID             identity.CommandID
	ID, OrganizationID    ID
	OrganizationVersion   uint64
	RecipientUserID       identity.UserID
	RecipientIdentifierID identity.IdentifierID
	CorrelationID         string
}

InvitationSendRequest binds a caller-preallocated canonical invitation ID, organization/version and exact existing verified recipient identifier. Only the static member role is selected; no caller role/team or address is accepted.

type InvitationService

type InvitationService struct {
	// contains filtered or unexported fields
}

InvitationService borrows the existing root service/session/policy and explicit repository/verifier. It owns no store, transaction, token engine, delivery worker or goroutine. Immutable construction supports concurrency.

func NewInvitationService

func NewInvitationService(base *Service, repository InvitationRepository, verifier InvitationVerifier, c InvitationConfig) (*InvitationService, error)

NewInvitationService validates the explicit finite profile without IO. The caller owns borrowed collaborators and their shutdown; none may be mutated concurrently with calls. Invalid or zero configuration has no defaults.

func (*InvitationService) Get

Get returns safe delivery/invitation metadata only to a current genuine owner. It never reveals the canonical address, bearer proof or encrypted custody.

func (*InvitationService) List

List returns an independently owned, bounded page of owner-only metadata. Actor removal/current policy is rechecked on every request, without caching.

func (*InvitationService) ListMine

ListMine authenticates the actual human recipient and current self-list policy. It needs no organization membership or recipient-selected address. Both decisions precede live session and exact recipient/page revalidation; withdrawal, outage, cancellation or original freshness expiry releases no page.

func (*InvitationService) PrepareAccept

func (s *InvitationService) PrepareAccept(ctx context.Context, r InvitationAcceptRequest) (out Command, err error)

PrepareAccept verifies but never consumes. Native execution must repeat current recipient/session/policy admission and atomically consume the shared capability with invitation, membership, scoped authority, audit and events.

func (*InvitationService) PrepareCancel

func (s *InvitationService) PrepareCancel(ctx context.Context, r InvitationCancelRequest) (out Command, err error)

PrepareCancel freezes current owner/administrator admission. The native owner must repeat it under its existing transaction and terminally advance the invitation version; cryptographic validity alone must never bypass that row.

func (*InvitationService) PrepareExpire

func (s *InvitationService) PrepareExpire(ctx context.Context, r InvitationExpireRequest) (out Command, err error)

PrepareExpire admits explicit expiry before or after the natural deadline. The operation catalog's pending-row transition differs from natural expiry's pending-version projection: native execution must atomically persist the next terminal version, reason, canonical audit and event, invalidating every proof.

func (*InvitationService) PrepareReject

func (s *InvitationService) PrepareReject(ctx context.Context, r InvitationRejectRequest) (out Command, err error)

PrepareReject freezes recipient admission without consuming a capability. Native execution must repeat current session, recipient and organization admission and atomically terminalize the pending row with audit and events.

func (*InvitationService) PrepareResend

func (s *InvitationService) PrepareResend(ctx context.Context, r InvitationResendRequest) (out Command, err error)

PrepareResend freezes current owner/administrator and original recipient admission without issuing a proof or writing state. The native adapter must atomically supersede the predecessor and issue/enqueue exactly one successor for version+1 through the existing capability, delivery and coordinator owners. The predecessor remains usable until that single commit, never alongside its successor. Historical recovery must not regenerate or redeliver either grant.

func (*InvitationService) PrepareSend

func (s *InvitationService) PrepareSend(ctx context.Context, r InvitationSendRequest) (out Command, err error)

type InvitationSnapshot

type InvitationSnapshot struct {
	Organization        Organization
	Actor, Target       Member
	RecipientUser       identity.User
	RecipientIdentifier identity.Identifier
	Record              InvitationDelivery
	PendingCount        uint64
}

InvitationSnapshot is adapter input, not a public metadata response or a permission/proof. Recipient fields are sensitive native owner projections; automatic serialization is denied. Durable application must fence their original verified identifier and independent security epochs with its owner.

func (InvitationSnapshot) Format

func (InvitationSnapshot) Format(s fmt.State, _ rune)

func (InvitationSnapshot) MarshalJSON

func (InvitationSnapshot) MarshalJSON() ([]byte, error)

func (InvitationSnapshot) MarshalText

func (InvitationSnapshot) MarshalText() ([]byte, error)

type InvitationStatus

type InvitationStatus uint8

InvitationStatus describes the selected durable lifecycle. Zero is invalid. Natural expiry retains its last issued version; explicit expiry is the next durable record version. IssuedVersion distinguishes those projections after resend, while legacy first-issuance rows retain version1/version2 semantics. No terminal state can become pending again.

const (
	InvitationPending InvitationStatus = iota + 1
	InvitationAccepted
	InvitationExpired
	InvitationCancelled
	InvitationRejected
)

type InvitationVerifier

type InvitationVerifier interface {
	Verify(context.Context, capability.Proof, capability.Purpose, string) (capability.Grant, error)
}

InvitationVerifier consumes the existing capability owner's non-consuming cryptographic verification contract. A verified grant is not permission or one-use consumption; those remain the native transaction owner's obligation.

type LeaveRequest

type LeaveRequest struct {
	Authentication      Authentication
	CommandID           identity.CommandID
	OrganizationID      ID
	OrganizationVersion uint64
	CorrelationID       string
}

LeaveRequest removes only the authenticated human's current membership in the explicitly selected organization. OrganizationVersion is its exact policy epoch; the current membership version is captured during preparation, never supplied as a target override. The final recovery-capable owner cannot leave. Keep CommandID across retries. Historical recovery still requires current membership; a retained receipt is not permission to read after leaving.

type LifecycleConfig

type LifecycleConfig struct {
	Version       uint64
	ProofAudience string
}

LifecycleConfig selects native archive/restore, not hard deletion or external integration cascades. Zero disables it. Version1 requires the same explicit signed-proof audience in both operation-specific password primary sources.

func (LifecycleConfig) Validate

func (c LifecycleConfig) Validate() error

Validate accepts the disabled zero policy or the explicit version1 audience.

type LifecycleRequest

type LifecycleRequest struct {
	Authentication        Authentication
	CommandID             identity.CommandID
	OrganizationID        ID
	OrganizationVersion   uint64
	Reason, CorrelationID string
	Proof                 authentication.ReauthenticationProof
}

LifecycleRequest changes one exact organization policy version. Reason is a bounded visible-ASCII code, not free text. Proof must be freshly issued for this exact action, current owner and session by the actual password owner. Copies of visible proof fields do not authenticate their signed provenance.

func (LifecycleRequest) Format

func (LifecycleRequest) Format(s fmt.State, _ rune)

func (LifecycleRequest) MarshalJSON

func (LifecycleRequest) MarshalJSON() ([]byte, error)

func (LifecycleRequest) MarshalText

func (LifecycleRequest) MarshalText() ([]byte, error)

type LifecycleState

type LifecycleState string

LifecycleState is current primary state, never authority inferred from a retained receipt. Deleted organizations are not discoverable in this profile.

const (
	LifecycleActive   LifecycleState = "active"
	LifecycleArchived LifecycleState = "archived"
)

type ListOrganizationsRequest

type ListOrganizationsRequest struct {
	Authentication Authentication
	Cursor         OrganizationCursor
	Limit          uint32
	CorrelationID  string
}

ListOrganizationsRequest lists only the current authenticated human's active organizations. Limit is 1..200 with no default (the operation's typed catalog bound). Cursor is empty or a canonical <=512-byte returned continuation. Pages use bytewise organization-ID order over current committed state; earlier additions require restarting. Unknown authority/errors yield no partial page.

type ListOrganizationsResult

type ListOrganizationsResult struct {
	Organizations []Organization
	NextCursor    OrganizationCursor
}

ListOrganizationsResult owns its bounded organization snapshots. NextCursor derives only from the last returned visible ID and is empty at the observed end. Neither a snapshot nor continuation is current authorization evidence.

type Member

type Member struct {
	OrganizationID                ID
	TenantID                      identity.TenantID
	UserID                        identity.UserID
	Role                          Role
	Version, AuthorizationVersion uint64
	Active                        bool
}

Member is a tenant/organization-bound membership snapshot. Removed bindings retain their version and organization-scoped authorization epoch as tombstones. Neither Role nor this structural value authenticates its human user.

type MemberRequest

type MemberRequest struct {
	Authentication                     Authentication
	CommandID                          identity.CommandID
	OrganizationID                     ID
	UserID                             identity.UserID
	Role                               Role
	OrganizationVersion, MemberVersion uint64
	CorrelationID                      string
}

MemberRequest binds an exact target and organization/member optimistic versions. Add requires Role and preserves an existing removed membership's version (zero only for a never-admitted user). Remove requires the active member's version; it never treats arbitrary absence as successful removal. Update requires a different valid static Role and the exact active membership version. Only owners may grant owner or change an owner; at least one owner (the static profile's recovery administrator) must survive every transition.

type Operation

type Operation string

Operation is the canonical callable operation, never selected from free text.

const (
	ReserveDomain  Operation = "identity.organization.domain.reserve"
	WithdrawDomain Operation = "identity.organization.domain.withdraw"
	GetDomainClaim Operation = "identity.organization.domain.get"
)
const (
	// ApplyDomainProof admits one trusted observation for an existing claim.
	ApplyDomainProof Operation = "identity.organization.domain.evidence.apply"
	// RevokeDomainProof terminally retires that exact proof without observing.
	RevokeDomainProof Operation = "identity.organization.domain.evidence.revoke"
)
const (
	CreateDynamicRole Operation = "identity.organization.role.create"
	UpdateDynamicRole Operation = "identity.organization.role.update"
	DeleteDynamicRole Operation = "identity.organization.role.delete"
	GetDynamicRole    Operation = "identity.organization.role.get"
	ListDynamicRoles  Operation = "identity.organization.role.list"
	AssignDynamicRole Operation = "identity.organization.role.assign"
	RevokeDynamicRole Operation = "identity.organization.role.revoke"
	CheckPermission   Operation = "identity.organization.permission-check"
)
const (
	InvitationSend   Operation = "identity.organization.invitation-send"
	InvitationAccept Operation = "identity.organization.invitation.accept"
	InvitationCancel Operation = "identity.organization.invitation.cancel"
	InvitationReject Operation = "identity.organization.invitation.reject"
	InvitationExpire Operation = "identity.organization.invitation.expire"
	InvitationResend Operation = "identity.organization.invitation.resend"
	InvitationGet    Operation = "identity.organization.invitation.get"
	InvitationList   Operation = "identity.organization.invitation.list"
)
const (
	Archive Operation = "identity.organization.archive"
	Restore Operation = "identity.organization.restore"
)
const (
	Create       Operation = "identity.organization.create"
	Update       Operation = "identity.organization.update"
	AddMember    Operation = "identity.organization.member.add"
	UpdateMember Operation = "identity.organization.member.update"
	RemoveMember Operation = "identity.organization.member.remove"
	LeaveMember  Operation = "identity.organization.member.leave"
	Get          Operation = "identity.organization.get"
	ListMembers  Operation = "identity.organization.member.list"
)
const (
	CreateTeam       Operation = "identity.organization.team.create"
	AddTeamMember    Operation = "identity.organization.team.member-add"
	RemoveTeamMember Operation = "identity.organization.team.member-remove"
	GetTeam          Operation = "identity.organization.team.get"
	UpdateTeam       Operation = "identity.organization.team.update"
	DeleteTeam       Operation = "identity.organization.team.delete"
	ListTeams        Operation = "identity.organization.team.list"
	ListMyTeams      Operation = "identity.organization.team.list-mine"
	ListTeamMembers  Operation = "identity.organization.team.member-list"
)
const InvitationListMine Operation = "identity.organization.invitation.list-mine"

InvitationListMine is the authenticated recipient's safe invitation listing, not an organization membership check or an administrator's recipient lookup.

const ListOrganizations Operation = "identity.organization.list"

ListOrganizations is the current authenticated subject's organization read. It is not an active-organization selection or an arbitrary-user lookup.

const OwnerTransfer Operation = "identity.organization.owner-transfer"

OwnerTransfer atomically exchanges the authenticated owner's role with an existing nonowner member. It never creates membership or adds another owner.

func (Operation) Descriptor

func (o Operation) Descriptor() identity.FeatureOperation

Descriptor identifies the one versioned storage-neutral mutation schema. Unknown and read operations return zero, which identity rejects.

type Organization

type Organization struct {
	ID         ID
	TenantID   identity.TenantID
	Slug, Name string
	Version    uint64
	Active     bool
}

Organization is one immutable primary snapshot, not an access decision. Version is also the organization policy/authority epoch in this profile.

type OrganizationCursor

type OrganizationCursor string

OrganizationCursor is an opaque, non-authoritative keyset locator. Empty starts a listing; only use a returned cursor unchanged. It binds tenant, authenticated subject and deployed policy, not a snapshot or membership grant.

type OrganizationListEntry

type OrganizationListEntry struct {
	Organization Organization
	Member       Member
}

OrganizationListEntry is an owned current active organization/self-member projection. Native readers must also verify that Member.AuthorizationVersion is the current organization-scoped authority, not an unrelated global epoch.

type OrganizationListQuery

type OrganizationListQuery struct {
	TenantID    identity.TenantID
	ActorID     identity.UserID
	After       ID
	MaximumRows uint32
}

OrganizationListQuery is a trusted storage query generated after session authentication. MaximumRows is the configured per-user membership cap (1..1000), not the requested output size. Return at most MaximumRows+1 candidates after the exclusive ID in ascending bytewise order; the extra row detects a changed deployment cap without falsely declaring EOF. No arbitrary target is exposed through the public authenticated list request.

type OrganizationListReader

type OrganizationListReader interface {
	ReadOrganizations(context.Context, OrganizationListQuery) ([]OrganizationListEntry, error)
}

OrganizationListReader is an additive optional repository capability. Existing Repository implementations remain source-compatible; selecting listing on a reader without this capability returns ErrUnavailable. Reads must honor ctx, release rows/locks before returning, and use current primary state without a positive cache. The authenticated service owns policy filtering/disclosure.

type OwnerTransferRequest

type OwnerTransferRequest struct {
	Authentication                                    Authentication
	CommandID                                         identity.CommandID
	OrganizationID                                    ID
	TargetUserID                                      identity.UserID
	OrganizationVersion, SourceVersion, TargetVersion uint64
	CorrelationID                                     string
	// Proof is the original password owner's operation-bound reauthentication
	// projection, not authenticated merely by these visible fields. The native
	// primary owner must authenticate and consume its exact original record.
	Proof authentication.ReauthenticationProof
}

OwnerTransferRequest binds the organization policy epoch and both membership versions. The source is always the authenticated session subject, never an actor supplied by the caller. TargetUserID must identify a different active member or administrator in the same organization. The former owner becomes RoleMember; both membership and scoped authorization versions advance. Preserve CommandID and the prepared command for retries or exact recovery.

type Permission

type Permission struct {
	ID           string
	Action       authorization.Action
	ResourceType authorization.ResourceType
}

Permission registers one exact action/resource type in the deployment's immutable custom-permission ceiling. No wildcard or organization-management action is admitted: custom roles never confer static owner/admin authority.

type PermissionCheckRequest

type PermissionCheckRequest struct {
	Authentication      Authentication
	OrganizationID      ID
	OrganizationVersion uint64
	Action              authorization.Action
	ResourceType        authorization.ResourceType
	ResourceID          authorization.ResourceID
	CorrelationID       string
}

PermissionCheckRequest checks the authenticated member's effective custom roles, never another subject. Team bindings participate only under explicit selection. OrganizationVersion is a required current policy fence. Static roles grant management only, not implicit application-resource access.

type Query

type Query struct {
	Operation        Operation
	TenantID         identity.TenantID
	ActorID          identity.UserID
	OrganizationID   ID
	TargetID         identity.UserID
	Cursor           identity.UserID
	Limit            uint32
	TeamID           ID
	TeamCursor       string
	DomainClaimID    ID
	DynamicRoleID    ID
	RoleCursor       string
	IncludeTeamRoles bool
	// Lifecycle permits owner-only archived discovery for Get/Archive/Restore.
	// It must never broaden an ordinary member/team/permission query.
	Lifecycle bool
}

Query is a bounded primary read generated by Service after authentication. Adapters must recheck membership on each call; there is no positive cache. TargetID is used only for native identity-owner reads when preparing mutation.

type ReadRequest

type ReadRequest struct {
	Authentication Authentication
	OrganizationID ID
	Cursor         identity.UserID
	Limit          uint32
	CorrelationID  string
}

ReadRequest chooses an exact organization. Cursor is an exclusive UserID in bytewise database C collation; Limit must be 1..100 for member listing.

type Repository

type Repository interface {
	Read(context.Context, Query) (View, error)
}

Repository reads only authoritative owned organization state and delegates target identity reads to identity's native owner. It owns no authentication engine. Implementations must honor deadlines and safely support concurrency.

type Role

type Role string

Role is a closed static role. Unknown values never grant permissions.

const (
	RoleOwner         Role = "owner"
	RoleAdministrator Role = "administrator"
	RoleMember        Role = "member"
)

func (Role) Valid

func (r Role) Valid() bool

type RoleBinding

type RoleBinding struct {
	RoleID, OrganizationID             ID
	TenantID                           identity.TenantID
	UserID                             identity.UserID
	Version, OrganizationMemberVersion uint64
	Active                             bool
}

RoleBinding binds one role to an exact current organization membership epoch. Removal/readmission or any static-role transition invalidates old bindings. Active describes the stored assignment state, not independent permission: current active membership and matching OrganizationMemberVersion still apply.

type RoleBindingRequest

type RoleBindingRequest struct {
	Authentication                                                              Authentication
	CommandID                                                                   identity.CommandID
	OrganizationID, RoleID                                                      ID
	UserID                                                                      identity.UserID
	OrganizationVersion, RoleVersion, OrganizationMemberVersion, BindingVersion uint64
	CorrelationID                                                               string
}

RoleBindingRequest assigns/revokes one direct role for an active member. BindingVersion is zero for first assignment, otherwise the current tombstone or live version; all supplied organization, role and member epochs must match.

type RoleStatement

type RoleStatement struct {
	PermissionID string
	ResourceID   authorization.ResourceID
	Effect       authorization.Outcome
}

RoleStatement references only registered permissions, an exact resource ID, and Allow or Deny. Conditions and wildcards remain unselected; optional team inheritance evaluates these same bounded statements without expanding them.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service owns bounded authentication and pure policy/preparation, not durable transactions. A native adapter supplies the scoped UnitOfWork to Execute. NewService retains borrowed immutable collaborators and starts no goroutines.

func NewService

func NewService(sessions *session.SessionService, repository Repository, policy authorization.Service, c Config) (*Service, error)

func (*Service) CheckPermission

func (s *Service) CheckPermission(ctx context.Context, r PermissionCheckRequest) (out authorization.Decision, err error)

CheckPermission never caches an allow. A policy callback can veto the check; the existing RBAC/deny-overrides engine decides only current exact statements. Returned Revision is the organization policy version, not a reusable lease.

func (*Service) Execute

func (s *Service) Execute(ctx context.Context, c Command, work UnitOfWork) (out StoreResult, err error)

Execute repeats current action policy with the original Service custody before borrowing the adapter's one-command work. Policy callbacks run before protected SQL; later lock waits require native final admission. It never retries an unknown result or replaces its command identity.

func (*Service) Get

func (s *Service) Get(ctx context.Context, r ReadRequest) (View, error)

func (*Service) GetDomainClaim

func (s *Service) GetDomainClaim(ctx context.Context, r DomainClaimReadRequest) (out View, err error)

GetDomainClaim discloses current custody only to a current owner/admin after current policy and a second primary membership/session read. It is not proof.

func (*Service) GetDynamicRole

func (s *Service) GetDynamicRole(ctx context.Context, r DynamicRoleReadRequest) (View, error)

GetDynamicRole returns one current custom role after late authority revalidation.

func (*Service) GetLifecycle

func (s *Service) GetLifecycle(ctx context.Context, r ReadRequest) (out View, err error)

GetLifecycle exposes active/archived metadata only to a current owner with a genuine session and current organization.get policy. Ordinary reads continue denying archived organizations. Use this exact version to prepare restoration.

func (*Service) GetTeam

func (s *Service) GetTeam(ctx context.Context, r TeamReadRequest) (View, error)

func (*Service) ListDynamicRoles

func (s *Service) ListDynamicRoles(ctx context.Context, r DynamicRoleReadRequest) (View, error)

ListDynamicRoles returns only current custom roles, not the closed static Role constants.

func (*Service) ListMembers

func (s *Service) ListMembers(ctx context.Context, r ReadRequest) (View, error)

func (*Service) ListMyTeams

func (s *Service) ListMyTeams(ctx context.Context, r TeamReadRequest) (View, error)

func (*Service) ListOrganizations

func (s *Service) ListOrganizations(ctx context.Context, r ListOrganizationsRequest) (out ListOrganizationsResult, err error)

ListOrganizations authenticates self and filters only explicit initial Deny decisions. Captured allows must still allow at final policy admission; current session, exact organization/member snapshots and original freshness are then rechecked after callbacks. Scan work is bounded by MaximumPerUser, never by an unbounded search for a full output page. No mutation or selection is performed.

func (*Service) ListTeamMembers

func (s *Service) ListTeamMembers(ctx context.Context, r TeamReadRequest) (View, error)

func (*Service) ListTeams

func (s *Service) ListTeams(ctx context.Context, r TeamReadRequest) (View, error)

func (*Service) PrepareAddMember

func (s *Service) PrepareAddMember(ctx context.Context, r MemberRequest) (Command, error)

func (*Service) PrepareAddTeamMember

func (s *Service) PrepareAddTeamMember(ctx context.Context, r TeamMemberRequest) (Command, error)

func (*Service) PrepareArchive

func (s *Service) PrepareArchive(ctx context.Context, r LifecycleRequest) (Command, error)

PrepareArchive freezes owner admission without changing authority. Execute atomically denies the organization and terminally cancels pending invitations.

func (*Service) PrepareAssignDynamicRole

func (s *Service) PrepareAssignDynamicRole(ctx context.Context, r RoleBindingRequest) (Command, error)

PrepareAssignDynamicRole freezes a direct grant for one exact active membership.

func (*Service) PrepareAssignTeamDynamicRole

func (s *Service) PrepareAssignTeamDynamicRole(ctx context.Context, r TeamRoleBindingRequest) (Command, error)

PrepareAssignTeamDynamicRole freezes a team-targeted role.assign command. TeamBindingsVersion1 and terminal team lifecycle must be selected explicitly.

func (*Service) PrepareCreate

func (s *Service) PrepareCreate(ctx context.Context, r CreateRequest) (out Command, err error)

func (*Service) PrepareCreateDynamicRole

func (s *Service) PrepareCreateDynamicRole(ctx context.Context, r DynamicRoleRequest) (Command, error)

PrepareCreateDynamicRole freezes a new role without reserving or persisting it.

func (*Service) PrepareCreateTeam

func (s *Service) PrepareCreateTeam(ctx context.Context, r CreateTeamRequest) (Command, error)

func (*Service) PrepareDeleteDynamicRole

func (s *Service) PrepareDeleteDynamicRole(ctx context.Context, r DynamicRoleRequest) (Command, error)

PrepareDeleteDynamicRole freezes terminal retirement, including all effective bindings.

func (*Service) PrepareDeleteTeam

func (s *Service) PrepareDeleteTeam(ctx context.Context, r DeleteTeamRequest) (Command, error)

func (*Service) PrepareDomainEvidence

func (s *Service) PrepareDomainEvidence(ctx context.Context, r DomainEvidenceRequest, observer DomainEvidenceObserver) (out DomainEvidenceCommand, err error)

PrepareDomainEvidence performs one explicitly trusted observation after current claim/session/policy admission. Execute must fence the original expiry again after lock waits. An observer failure leaves no durable command or authority.

func (*Service) PrepareLeaveMember

func (s *Service) PrepareLeaveMember(ctx context.Context, r LeaveRequest) (Command, error)

PrepareLeaveMember authenticates self, checks current membership/action policy and freezes its exact membership snapshot without writing. Durable adapters must fence that same actor and the final owner under the shared aggregate lock.

func (*Service) PrepareOwnerTransfer

func (s *Service) PrepareOwnerTransfer(ctx context.Context, r OwnerTransferRequest) (out Command, err error)

PrepareOwnerTransfer requires a fresh, current session and current owner membership. Preparation writes nothing. The adapter must protect both exact memberships, target identity and session through its sole commit boundary.

func (*Service) PrepareRemoveMember

func (s *Service) PrepareRemoveMember(ctx context.Context, r MemberRequest) (Command, error)

func (*Service) PrepareRemoveTeamMember

func (s *Service) PrepareRemoveTeamMember(ctx context.Context, r TeamMemberRequest) (Command, error)

func (*Service) PrepareReserveDomain

func (s *Service) PrepareReserveDomain(ctx context.Context, r DomainClaimRequest) (Command, error)

func (*Service) PrepareRestore

func (s *Service) PrepareRestore(ctx context.Context, r LifecycleRequest) (Command, error)

PrepareRestore enables only surviving native child state. It never recreates removed members, deleted teams/roles, withdrawn claims or canceled invitations.

func (*Service) PrepareRevokeDomainEvidence

func (s *Service) PrepareRevokeDomainEvidence(ctx context.Context, r DomainEvidenceRequest, key DomainProofKey) (out DomainEvidenceCommand, err error)

PrepareRevokeDomainEvidence freezes terminal revocation of the exact primary proof. It performs no external observation and cannot refresh original times.

func (*Service) PrepareRevokeDynamicRole

func (s *Service) PrepareRevokeDynamicRole(ctx context.Context, r RoleBindingRequest) (Command, error)

PrepareRevokeDynamicRole freezes removal of one exact current direct binding.

func (*Service) PrepareRevokeTeamDynamicRole

func (s *Service) PrepareRevokeTeamDynamicRole(ctx context.Context, r TeamRoleBindingRequest) (Command, error)

PrepareRevokeTeamDynamicRole freezes removal of one exact team-role binding.

func (*Service) PrepareUpdate

func (s *Service) PrepareUpdate(ctx context.Context, r UpdateRequest) (out Command, err error)

PrepareUpdate authenticates the human actor and checks current organization membership and action policy. Preparation writes nothing; the native adapter must recheck current authority and the expected version under its own locks.

func (*Service) PrepareUpdateDynamicRole

func (s *Service) PrepareUpdateDynamicRole(ctx context.Context, r DynamicRoleRequest) (Command, error)

PrepareUpdateDynamicRole freezes a complete replacement at exact role/organization epochs.

func (*Service) PrepareUpdateMember

func (s *Service) PrepareUpdateMember(ctx context.Context, r MemberRequest) (Command, error)

PrepareUpdateMember freezes one exact active membership's static role change. Current owners and administrators may change nonowners; only owners may grant or demote owners. Preparation writes nothing. The native owner must serialize the last-owner invariant with removal, leave and ownership transfer and advance both organization policy and the changed subject's scoped authority atomically.

func (*Service) PrepareUpdateTeam

func (s *Service) PrepareUpdateTeam(ctx context.Context, r UpdateTeamRequest) (Command, error)

func (*Service) PrepareWithdrawDomain

func (s *Service) PrepareWithdrawDomain(ctx context.Context, r DomainClaimRequest) (Command, error)

type StoreResult

type StoreResult struct {
	Receipt        identity.CommandResult
	OrganizationID ID
	MemberID       identity.UserID
	// InvitationID is set only for invitation commands, including unknown
	// outcomes. It identifies the original command, never a delivery claim.
	InvitationID ID
}

StoreResult retains the existing identity-owned outcome protocol. A committed result is historical, not current membership, a fresh grant or a new session. Unknown must be reconciled using the same command; never allocate a retry ID.

type Team

type Team struct {
	ID, OrganizationID ID
	TenantID           identity.TenantID
	Name               string
	Version            uint64
}

Team is a tenant/organization-bound snapshot. Version advances on membership changes; Organization.Version is the shared policy epoch, not this local value.

type TeamConfig

type TeamConfig struct {
	Version, MaximumTeams, MaximumMembers uint64
	// LifecycleVersion 1 selects get/update/terminal-delete after the native
	// forward migration. Zero preserves the original membership-only profile
	// and its serialized configuration and command framing.
	LifecycleVersion uint64 `json:",omitempty"`
}

TeamConfig explicitly selects the finite membership-only profile. Zero disables it. Version identifies the immutable deployed team policy; quotas have no defaults. Teams never grant organization roles or independent permissions. MaximumTeams follows REFERENCE_CONFIGURATION organization.max_teams (10,000). MaximumMembers is a local storage/cardinality budget capped at the existing 100,000 organization-member ceiling, not a protocol-defined team default.

func (TeamConfig) Validate

func (c TeamConfig) Validate() error

type TeamMember

type TeamMember struct {
	TeamID, OrganizationID             ID
	TenantID                           identity.TenantID
	UserID                             identity.UserID
	Version, OrganizationMemberVersion uint64
	Active                             bool
}

TeamMember retains removal tombstones. OrganizationMemberVersion binds the exact admission, so removing and readmitting an org member cannot revive teams.

type TeamMemberRequest

type TeamMemberRequest struct {
	Authentication                                                                 Authentication
	CommandID                                                                      identity.CommandID
	OrganizationID, TeamID                                                         ID
	UserID                                                                         identity.UserID
	OrganizationVersion, TeamVersion, OrganizationMemberVersion, MembershipVersion uint64
	CorrelationID                                                                  string
}

TeamMemberRequest binds exact organization/team/member epochs. Add requires zero MembershipVersion for a never-bound user or the previous tombstone version. Remove requires the current binding; replay uses the original prepared command.

type TeamReadRequest

type TeamReadRequest struct {
	Authentication         Authentication
	OrganizationID, TeamID ID
	Cursor                 string
	Limit                  uint32
	CorrelationID          string
}

TeamReadRequest selects an exact team or a paginated team read. GetTeam requires TeamID, Limit zero, and an empty Cursor. Paginated reads use exclusive bytewise ID keysets, 1..100 per page; only ListTeamMembers requires TeamID. Members may read their own team's members; only owners/admins may read all organization teams or other teams.

type TeamRoleBinding

type TeamRoleBinding struct {
	RoleID, TeamID, OrganizationID ID
	TenantID                       identity.TenantID
	Version                        uint64
	Active                         bool
}

TeamRoleBinding is one organization-scoped custom-role assignment to a terminal team identity. Active is stored assignment state, not a membership grant: effective access also requires current compatible org/team membership. Version zero denotes never assigned; revoked bindings retain their version.

type TeamRoleBindingRequest

type TeamRoleBindingRequest struct {
	Authentication                                                Authentication
	CommandID                                                     identity.CommandID
	OrganizationID, RoleID, TeamID                                ID
	OrganizationVersion, RoleVersion, TeamVersion, BindingVersion uint64
	CorrelationID                                                 string
}

TeamRoleBindingRequest assigns/revokes one role for an exact current team. OrganizationVersion, RoleVersion, TeamVersion and BindingVersion are obtained from GetDynamicRole with TargetTeamID, including retained binding tombstones. It never names or impersonates a human target and confers no manager authority.

type UnitOfWork

type UnitOfWork interface {
	Execute(context.Context, Command) (StoreResult, error)
	Recover(context.Context, identity.CommandID) (StoreResult, error)
}

UnitOfWork is invocation-scoped, storage-neutral mutation/recovery authority. The native adapter binds it immutably to one original command and current admitted caller. Recover must reject any other ID, never perform global lookup or replay effects, and preserve committed/not-committed/unknown distinctions.

type UpdateRequest

type UpdateRequest struct {
	Authentication            Authentication
	CommandID                 identity.CommandID
	OrganizationID            ID
	OrganizationVersion       uint64
	Name, Slug, CorrelationID string
}

UpdateRequest replaces only Name and Slug using CreateRequest's bounds and case-sensitive semantics; it neither clears nor accepts other metadata. OrganizationVersion is the exact current organization/policy epoch. Owners and administrators may update; identical metadata returns ErrConflict without a transition, new epoch, audit or lifecycle event. Preserve CommandID across retries and reconcile ambiguous outcomes using the native adapter's recovery.

type UpdateTeamRequest

type UpdateTeamRequest struct {
	Authentication                   Authentication
	CommandID                        identity.CommandID
	OrganizationID, TeamID           ID
	OrganizationVersion, TeamVersion uint64
	Name, CorrelationID              string
}

UpdateTeamRequest replaces only the bounded Name. Both epochs must match; identical names conflict without advancing epochs or emitting effects.

type View

type View struct {
	Organization    Organization
	Actor, Target   Member
	TargetUser      identity.User
	OwnerCount      uint64
	Members         []Member
	NextCursor      identity.UserID
	Team            Team
	TeamMember      TeamMember
	Teams           []Team
	TeamMembers     []TeamMember
	NextTeamCursor  string
	DomainClaim     DomainClaim
	DynamicRole     DynamicRole
	DynamicRoles    []DynamicRole
	RoleBinding     RoleBinding
	TeamRoleBinding TeamRoleBinding
	NextRoleCursor  string
	LifecycleState  LifecycleState
	LifecycleReason string
}

View is an independently owned primary snapshot. OwnerCount includes active owners only; it is advisory until rechecked under the mutation's aggregate lock.

Directories

Path Synopsis
postgres module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL