Documentation
¶
Overview ¶
Package tenancy provides explicit tenant identity, propagation, and isolation contracts. Tenant identifiers are routing data, never proof of authentication, membership, or authorization.
Example ¶
package main
import (
"context"
"fmt"
"github.com/faustbrian/go-tenancy"
)
func main() {
id, err := tenancy.ParseTenantID("customer-42")
if err != nil {
panic(err)
}
scope, err := tenancy.NewTenantScope(id, tenancy.Metadata{})
if err != nil {
panic(err)
}
ctx, err := tenancy.WithScope(context.Background(), scope)
if err != nil {
panic(err)
}
resolved, err := tenancy.RequireTenant(ctx)
if err != nil {
panic(err)
}
fmt.Println(resolved.Equal(id))
}
Output: true
Index ¶
- Constants
- Variables
- func AssertScope(ctx context.Context, expected Scope) error
- func AssertTenant(ctx context.Context, expected TenantID) error
- func RunScoped(ctx context.Context, scope Scope, operation func(context.Context) error) error
- func WithScope(ctx context.Context, scope Scope) (context.Context, error)
- type AdministrativeAudit
- type AdministrativeReason
- func (reason AdministrativeReason) Actor() string
- func (reason AdministrativeReason) GoString() string
- func (reason AdministrativeReason) LogValue() slog.Value
- func (reason AdministrativeReason) Purpose() string
- func (reason AdministrativeReason) Reference() string
- func (reason AdministrativeReason) String() string
- type Boundary
- type Carrier
- type Group
- type GroupOptions
- type Integration
- func (integration *Integration) Boundary() Boundary
- func (integration *Integration) Key(encoder *NamespaceEncoder, scope Scope, logicalKey string) (string, error)
- func (integration *Integration) Receive(ctx context.Context, carrier Carrier, trusted bool) (context.Context, error)
- func (integration *Integration) Send(ctx context.Context, carrier Carrier) error
- type IterationOptions
- type IterationResult
- type MapCarrier
- type Metadata
- type NamespaceDomain
- type NamespaceEncoder
- type PropagationCodec
- func (codec *PropagationCodec) Accept(ctx context.Context, carrier Carrier, trusted bool) (context.Context, error)
- func (codec *PropagationCodec) Extract(carrier Carrier, trusted bool) (Scope, error)
- func (codec *PropagationCodec) Inject(carrier Carrier, scope Scope) error
- func (codec *PropagationCodec) InjectFromContext(carrier Carrier, ctx context.Context) error
- type PropagationOptions
- type ResumeToken
- type Scope
- func NewSystemScope(capability SystemCapability, metadata Metadata) (Scope, error)
- func NewTenantScope(id TenantID, metadata Metadata) (Scope, error)
- func NewUnscopedScope(reason AdministrativeReason, metadata Metadata) (Scope, error)
- func RequireScope(ctx context.Context) (Scope, error)
- func RequireSystem(ctx context.Context) (Scope, error)
- func RequireUnscoped(ctx context.Context) (Scope, error)
- func ScopeFromContext(ctx context.Context) (Scope, bool)
- func (scope Scope) AdministrativeReason() AdministrativeReason
- func (scope Scope) Equal(other Scope) bool
- func (scope Scope) GoString() string
- func (scope Scope) Kind() ScopeKind
- func (scope Scope) LogValue() slog.Value
- func (scope Scope) Metadata() Metadata
- func (scope Scope) String() string
- func (scope Scope) TenantID() TenantID
- func (scope Scope) Valid() bool
- type ScopeKind
- type SystemCapability
- type TenantAssertFunc
- type TenantAsserter
- type TenantID
- func (id TenantID) Equal(other TenantID) bool
- func (id TenantID) GoString() string
- func (id TenantID) LogValue() slog.Value
- func (id TenantID) MarshalJSON() ([]byte, error)
- func (id TenantID) MarshalText() ([]byte, error)
- func (id TenantID) Redacted() string
- func (id TenantID) String() string
- func (id *TenantID) UnmarshalJSON(data []byte) error
- func (id *TenantID) UnmarshalText(text []byte) error
- func (id TenantID) Valid() bool
- func (id TenantID) Value() string
- type TenantPage
- type TenantPager
Examples ¶
Constants ¶
const DefaultTenantField = "tenant_id"
DefaultTenantField is the transport-neutral metadata field name.
const (
// MaxTenantIDBytes is the fixed wire and storage bound for tenant IDs.
MaxTenantIDBytes = 128
)
Variables ¶
var ( // ErrInvalidGroup reports invalid ownership, limits, or receiver state. ErrInvalidGroup = errors.New("tenancy: invalid background group") // ErrGroupClosed reports submission after graceful close or shutdown begins. ErrGroupClosed = errors.New("tenancy: background group closed") )
var ( // ErrInvalidContext reports a nil context or invalid scope input. ErrInvalidContext = errors.New("tenancy: invalid context") // ErrScopeRequired reports an operation without explicit scope. ErrScopeRequired = errors.New("tenancy: scope required") // ErrTenantScopeRequired reports an operation without tenant-bound scope. ErrTenantScopeRequired = errors.New("tenancy: tenant scope required") // ErrSystemScopeRequired reports an operation without system-wide scope. ErrSystemScopeRequired = errors.New("tenancy: system scope required") // ErrConflictingScope reports an attempt to replace an existing scope. ErrConflictingScope = errors.New("tenancy: conflicting scope") )
var ( // ErrInvalidNamespaceKey reports a missing, weak, or oversized HMAC key. ErrInvalidNamespaceKey = errors.New("tenancy: invalid namespace key") // ErrInvalidNamespaceInput reports invalid scope, domain, or logical key. ErrInvalidNamespaceInput = errors.New("tenancy: invalid namespace input") )
var ( // ErrInvalidPropagation reports invalid codec or carrier configuration. ErrInvalidPropagation = errors.New("tenancy: invalid propagation") // ErrTenantMetadataMissing reports absent required tenant metadata. ErrTenantMetadataMissing = errors.New("tenancy: tenant metadata missing") // ErrTenantMetadataDuplicate reports repeated identical metadata. ErrTenantMetadataDuplicate = errors.New("tenancy: tenant metadata duplicate") // ErrTenantMetadataConflicting reports repeated distinct metadata. ErrTenantMetadataConflicting = errors.New("tenancy: tenant metadata conflicting") // ErrTenantMetadataOversized reports too many values at one boundary. ErrTenantMetadataOversized = errors.New("tenancy: tenant metadata oversized") // ErrTenantMetadataUntrusted reports metadata from an untrusted boundary. ErrTenantMetadataUntrusted = errors.New("tenancy: tenant metadata untrusted") // ErrTenantMetadataOverwrite reports injection into a populated field. ErrTenantMetadataOverwrite = errors.New("tenancy: tenant metadata overwrite") )
var ( // ErrInvalidMetadata reports malformed or unbounded optional metadata. ErrInvalidMetadata = errors.New("tenancy: invalid metadata") // ErrCapabilityRequired reports an attempt to construct system scope without // an explicit capability. ErrCapabilityRequired = errors.New("tenancy: system capability required") // ErrInvalidAdministrativeReason reports missing or malformed audit intent. ErrInvalidAdministrativeReason = errors.New("tenancy: invalid administrative reason") )
var ErrInvalidIntegration = errors.New("tenancy: invalid integration")
ErrInvalidIntegration reports an unknown or incomplete integration boundary.
var ErrInvalidIteration = errors.New("tenancy: invalid administrative iteration")
ErrInvalidIteration reports an unsafe administrative iteration contract.
var ErrInvalidOperation = errors.New("tenancy: invalid operation")
ErrInvalidOperation reports a nil scoped callback.
var ErrInvalidTenantID = errors.New("tenancy: invalid tenant ID")
ErrInvalidTenantID reports an empty, oversized, or non-canonical tenant ID.
var ErrTenantMismatch = errors.New("tenancy: tenant mismatch")
ErrTenantMismatch reports cross-tenant use without disclosing either ID.
Functions ¶
func AssertScope ¶
AssertScope fails closed unless ctx contains exactly expected scope.
func AssertTenant ¶
AssertTenant fails closed unless ctx is tenant-bound to expected.
func RunScoped ¶
RunScoped invokes operation synchronously with explicit immutable scope. It derives from ctx, so cancellation, deadlines, and caller values are retained.
Types ¶
type AdministrativeAudit ¶
type AdministrativeAudit func(context.Context, AdministrativeReason, TenantID) error
AdministrativeAudit records system intent before each tenant operation. Implementations own durability and redaction. Returning an error fails closed.
type AdministrativeReason ¶
type AdministrativeReason struct {
// contains filtered or unexported fields
}
AdministrativeReason is auditable intent for exceptional operations. It is not an authorization decision.
func NewAdministrativeReason ¶
func NewAdministrativeReason(actor, purpose, reference string) (AdministrativeReason, error)
NewAdministrativeReason validates explicit exceptional-operation intent.
func (AdministrativeReason) Actor ¶
func (reason AdministrativeReason) Actor() string
Actor identifies the accountable administrative caller.
func (AdministrativeReason) GoString ¶
func (reason AdministrativeReason) GoString() string
GoString returns a redacted representation for Go-syntax diagnostics.
func (AdministrativeReason) LogValue ¶
func (reason AdministrativeReason) LogValue() slog.Value
LogValue redacts administrative intent when passed directly to log/slog.
func (AdministrativeReason) Purpose ¶
func (reason AdministrativeReason) Purpose() string
Purpose describes why exceptional scope is required.
func (AdministrativeReason) Reference ¶
func (reason AdministrativeReason) Reference() string
Reference returns an optional external audit or change reference.
func (AdministrativeReason) String ¶
func (reason AdministrativeReason) String() string
String returns a redacted representation for diagnostics.
type Boundary ¶
type Boundary string
Boundary names a first-party propagation and namespace integration.
const ( // BoundaryQueue identifies queued deliveries and retries. BoundaryQueue Boundary = "queue" // BoundaryOutbox identifies transactional outbox records. BoundaryOutbox Boundary = "outbox" // BoundaryKafka identifies Kafka records. BoundaryKafka Boundary = "kafka" // BoundaryCloudEvents identifies CloudEvents attributes and extensions. BoundaryCloudEvents Boundary = "cloudevents" // BoundaryAudit identifies audit records. BoundaryAudit Boundary = "audit" // BoundaryCorrelation identifies correlation integration metadata. BoundaryCorrelation Boundary = "correlation" // BoundaryIdempotency identifies idempotency records. BoundaryIdempotency Boundary = "idempotency" // BoundaryCache identifies cache keys. BoundaryCache Boundary = "cache" // BoundaryRateLimit identifies rate-limit keys. BoundaryRateLimit Boundary = "rate-limit" // BoundarySearch identifies search indexes and documents. BoundarySearch Boundary = "search" // BoundaryScheduler identifies scheduled executions. BoundaryScheduler Boundary = "scheduler" // BoundaryWorkflow identifies workflow executions. BoundaryWorkflow Boundary = "workflow" // BoundaryEventSourcing identifies event streams and aggregates. BoundaryEventSourcing Boundary = "event-sourcing" // BoundaryTelemetry identifies telemetry attributes and links. BoundaryTelemetry Boundary = "telemetry" )
type Carrier ¶
Carrier is an explicit metadata boundary. Values must return an immutable, transport-bounded view or copy and no more than nine values; Set must replace the field with exactly one value.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group owns every goroutine it starts. Submit is bounded and cancellable; callers must invoke Drain for graceful completion or Shutdown for cancellation. Concurrent lifecycle calls join one terminal state while each call observes its own wait context, and any Shutdown cancels the shared task context. No task scope is retained between submissions.
func NewGroup ¶
func NewGroup(parent context.Context, options GroupOptions) (*Group, error)
NewGroup derives task lifetime from parent and validates a hard concurrency bound. The group owns the derived cancellation function.
func (*Group) Drain ¶ added in v1.1.0
Drain stops new submissions and waits for active tasks without cancelling them. Waiting observes ctx. Drain is safe for concurrent and repeated calls; once accepted work completes, it releases the group-owned task context. A concurrent Shutdown cancels shared tasks while Drain continues waiting for the same terminal state.
func (*Group) Shutdown ¶
Shutdown stops new submissions, cancels active tasks, and waits for their return. Waiting observes ctx, while task cancellation uses the group context. Concurrent and repeated lifecycle calls join the same terminal state; any Shutdown makes an active Drain or Close forceful by cancelling shared tasks.
func (*Group) Submit ¶
func (group *Group) Submit( submitCtx context.Context, scope Scope, operation func(context.Context) error, ) error
Submit validates and installs scope synchronously before acquiring bounded capacity. The task preserves submitCtx values, deadline, and cancellation while also remaining owned and cancellable by the group lifetime.
type GroupOptions ¶
GroupOptions define bounded concurrency and task error ownership. HandleError may be invoked concurrently by independently completing tasks.
type Integration ¶
type Integration struct {
// contains filtered or unexported fields
}
Integration gives queue, outbox, Kafka, CloudEvents, audit, correlation, idempotency, cache, rate-limit, search, scheduler, workflow, event-sourcing, and telemetry adapters one small explicit contract.
func NewIntegration ¶
func NewIntegration(boundary Boundary, options PropagationOptions) (*Integration, error)
NewIntegration validates a semantic boundary and propagation policy.
func (*Integration) Boundary ¶
func (integration *Integration) Boundary() Boundary
Boundary returns the immutable semantic integration name.
func (*Integration) Key ¶
func (integration *Integration) Key( encoder *NamespaceEncoder, scope Scope, logicalKey string, ) (string, error)
Key creates a boundary-separated opaque namespace for scope and logicalKey.
type IterationOptions ¶
type IterationOptions struct {
PageSize int
MaxTenants int
Resume ResumeToken
Audit AdministrativeAudit
}
IterationOptions bound work and make audit integration mandatory.
type IterationResult ¶
type IterationResult struct {
Processed int
Resume ResumeToken
Complete bool
}
IterationResult reports this run's progress and exact continuation point.
func IterateTenants ¶
func IterateTenants( ctx context.Context, system Scope, source TenantPager, options IterationOptions, operation func(context.Context, TenantID) error, ) (IterationResult, error)
IterateTenants executes bounded sequential work from an unscoped base context. system must be an explicit system scope. Audit callbacks receive a system-scoped child; operations receive a fresh tenant-scoped child derived from the original base, so tenant state cannot survive between iterations.
type MapCarrier ¶
MapCarrier is an owned in-memory carrier useful for message metadata and adapters. Values returns a copy so callers cannot mutate stored metadata.
func (MapCarrier) Set ¶
func (carrier MapCarrier) Set(field, value string)
Set replaces field with exactly one value.
func (MapCarrier) Values ¶
func (carrier MapCarrier) Values(field string) []string
Values returns an independently owned copy of a field's values.
type Metadata ¶
type Metadata struct {
// contains filtered or unexported fields
}
Metadata is immutable optional routing metadata. It is copied at every ownership boundary and must not carry authorization decisions or secrets.
func NewMetadata ¶
NewMetadata validates and owns a copy of values.
type NamespaceDomain ¶
type NamespaceDomain string
NamespaceDomain separates independently owned isolation key spaces.
const ( // NamespaceCache isolates cache entries. NamespaceCache NamespaceDomain = "cache" // NamespaceIdempotency isolates idempotency records. NamespaceIdempotency NamespaceDomain = "idempotency" // NamespaceRateLimit isolates rate-limit counters. NamespaceRateLimit NamespaceDomain = "rate-limit" // NamespaceSearch isolates search indexes and documents. NamespaceSearch NamespaceDomain = "search" // NamespaceQueue isolates queue routing and deduplication keys. NamespaceQueue NamespaceDomain = "queue" // NamespaceScheduler isolates scheduled work. NamespaceScheduler NamespaceDomain = "scheduler" // NamespaceEvent isolates event streams and event identities. NamespaceEvent NamespaceDomain = "event" // NamespaceWorkflow isolates workflow executions. NamespaceWorkflow NamespaceDomain = "workflow" // NamespaceTelemetry produces opaque telemetry attributes. NamespaceTelemetry NamespaceDomain = "telemetry" )
type NamespaceEncoder ¶
type NamespaceEncoder struct {
// contains filtered or unexported fields
}
NamespaceEncoder creates opaque, collision-resistant, versioned keys. It owns a copy of its HMAC key and is safe for concurrent use.
Example ¶
package main
import (
"fmt"
"github.com/faustbrian/go-tenancy"
)
func main() {
encoder, err := tenancy.NewNamespaceEncoder([]byte("0123456789abcdef0123456789abcdef"))
if err != nil {
panic(err)
}
scope, _ := tenancy.NewTenantScope(tenancy.MustTenantID("customer-42"), tenancy.Metadata{})
key, err := encoder.Encode(scope, tenancy.NamespaceCache, "orders/17")
if err != nil {
panic(err)
}
fmt.Println(len(key), key[:4])
}
Output: 68 tn2_
func NewNamespaceEncoder ¶
func NewNamespaceEncoder(key []byte) (*NamespaceEncoder, error)
NewNamespaceEncoder validates and copies a secret HMAC key.
func (*NamespaceEncoder) Encode ¶
func (encoder *NamespaceEncoder) Encode(scope Scope, domain NamespaceDomain, key string) (string, error)
Encode composes scope, domain, and key with length-delimited HMAC input. Raw tenant IDs and logical keys never appear in the returned namespace.
type PropagationCodec ¶
type PropagationCodec struct {
// contains filtered or unexported fields
}
PropagationCodec extracts and injects tenant-bound scope without deciding whether an inbound carrier is trusted.
func NewPropagationCodec ¶
func NewPropagationCodec(options PropagationOptions) (*PropagationCodec, error)
NewPropagationCodec validates and copies codec configuration.
func (*PropagationCodec) Accept ¶
func (codec *PropagationCodec) Accept( ctx context.Context, carrier Carrier, trusted bool, ) (context.Context, error)
Accept extracts trusted tenant metadata and installs it without replacing an existing context scope.
func (*PropagationCodec) Extract ¶
func (codec *PropagationCodec) Extract(carrier Carrier, trusted bool) (Scope, error)
Extract parses exactly one tenant value after the immediate boundary has explicitly established trust. Presence alone never establishes trust.
func (*PropagationCodec) Inject ¶
func (codec *PropagationCodec) Inject(carrier Carrier, scope Scope) error
Inject writes one tenant value and refuses overwrite or exceptional scope.
func (*PropagationCodec) InjectFromContext ¶
func (codec *PropagationCodec) InjectFromContext(carrier Carrier, ctx context.Context) error
InjectFromContext requires and injects tenant-bound context scope.
type PropagationOptions ¶
type PropagationOptions struct {
Field string
}
PropagationOptions configure an immutable tenant field name.
type ResumeToken ¶
ResumeToken identifies an exact page position. Offset is the next tenant in the page at Cursor, allowing retry after partial-page failure.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is an immutable explicit operation scope. Its zero value is invalid.
func NewSystemScope ¶
func NewSystemScope(capability SystemCapability, metadata Metadata) (Scope, error)
NewSystemScope requires an explicit, valid system capability.
func NewTenantScope ¶
NewTenantScope constructs work isolated to exactly id.
func NewUnscopedScope ¶
func NewUnscopedScope(reason AdministrativeReason, metadata Metadata) (Scope, error)
NewUnscopedScope constructs deliberately non-tenant work with an audit reason.
func RequireScope ¶
RequireScope fails closed when ctx has no explicit valid scope.
func RequireSystem ¶
RequireSystem returns scope only for explicitly capable system-wide work.
func RequireUnscoped ¶
RequireUnscoped returns scope only for intentionally unscoped work.
func ScopeFromContext ¶
ScopeFromContext retrieves explicit scope without treating absence as valid.
func (Scope) AdministrativeReason ¶
func (scope Scope) AdministrativeReason() AdministrativeReason
AdministrativeReason returns exceptional-operation intent. Tenant scope returns the zero reason.
type ScopeKind ¶
type ScopeKind uint8
ScopeKind distinguishes tenant-bound, system-wide, and deliberately unscoped work. The zero value is invalid.
type SystemCapability ¶
type SystemCapability struct {
// contains filtered or unexported fields
}
SystemCapability makes system-wide intent visible in construction APIs. It does not grant permission; applications must authorize its creation.
func NewSystemCapability ¶
func NewSystemCapability(reason AdministrativeReason) SystemCapability
NewSystemCapability records the audited intent used by NewSystemScope.
func (SystemCapability) GoString ¶
func (capability SystemCapability) GoString() string
GoString returns a redacted representation for Go-syntax diagnostics.
func (SystemCapability) LogValue ¶
func (capability SystemCapability) LogValue() slog.Value
LogValue redacts the capability when passed directly to log/slog.
func (SystemCapability) String ¶
func (capability SystemCapability) String() string
String returns a redacted representation for diagnostics.
type TenantAssertFunc ¶
TenantAssertFunc adapts a function to TenantAsserter.
func (TenantAssertFunc) AssertTenant ¶
func (assert TenantAssertFunc) AssertTenant(ctx context.Context, expected TenantID) error
AssertTenant implements TenantAsserter.
type TenantAsserter ¶
TenantAsserter is the small consumer-facing application and persistence boundary for operations that require one expected tenant.
type TenantID ¶
type TenantID struct {
// contains filtered or unexported fields
}
TenantID is a validated opaque tenant identifier. Its zero value is invalid. IDs use the case-sensitive ASCII alphabet [A-Za-z0-9._:/-], must begin with an alphanumeric byte, and are preserved exactly: no case folding, trimming, or Unicode normalization occurs.
String deliberately returns a redacted value. Use Value, MarshalText, or MarshalJSON only at an explicit trusted propagation or persistence boundary.
func MustTenantID ¶
MustTenantID is ParseTenantID for static configuration and tests.
func ParseTenantID ¶
ParseTenantID validates value without changing it.
func RequireTenant ¶
RequireTenant returns the tenant ID only for tenant-bound scope.
func (TenantID) MarshalJSON ¶
MarshalJSON serializes the canonical raw identifier as a JSON string.
func (TenantID) MarshalText ¶
MarshalText serializes the canonical raw identifier for a trusted boundary.
func (*TenantID) UnmarshalJSON ¶
UnmarshalJSON validates a JSON string and clears the receiver on failure.
func (*TenantID) UnmarshalText ¶
UnmarshalText validates input and clears the receiver on every failure.
type TenantPage ¶
TenantPage is one bounded page from a resumable tenant source. NextCursor is empty only when the source is complete.
type TenantPager ¶
TenantPager is the consumer-defined source for bounded administrative work.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package tenancyhttp provides explicit tenant propagation for net/http.
|
Package tenancyhttp provides explicit tenant propagation for net/http. |
|
Package tenancyjsonrpc propagates tenant scope through a bounded JSON-RPC metadata object.
|
Package tenancyjsonrpc propagates tenant scope through a bounded JSON-RPC metadata object. |
|
Package tenancypostgres provides explicit PostgreSQL predicates, transaction-local tenant settings, and Row-Level Security plans.
|
Package tenancypostgres provides explicit PostgreSQL predicates, transaction-local tenant settings, and Row-Level Security plans. |
|
Package tenancytest provides concise constructors and assertions for tests that exercise explicit tenancy contracts.
|
Package tenancytest provides concise constructors and assertions for tests that exercise explicit tenancy contracts. |