API Reference
Complete reference for all exported types, functions, and interfaces in Pericarp.
Package ddd
import "github.com/akeemphilbert/pericarp/pkg/ddd"
Provides the base aggregate root for domain entities with event sourcing capabilities.
Types
BaseEntity
type BaseEntity struct { /* unexported fields */ }
Embed in your aggregate root to gain event tracking, sequence number management, and uncommitted event collection. Thread-safe.
New aggregates start at sequence number -1. The first recorded event gets sequence number 0.
Sentinel Errors
var ErrWrongAggregate = errors.New("event does not belong to this aggregate")
var ErrDuplicateEvent = errors.New("event has already been applied")
var ErrInvalidEventSequenceNo = errors.New("event sequence number is invalid")
Functions
NewBaseEntity
func NewBaseEntity(aggregateID string) *BaseEntity
Creates a new BaseEntity with the given aggregate ID, starting at sequence number -1.
Methods on BaseEntity
GetID
func (e *BaseEntity) GetID() string
Returns the aggregate ID. Thread-safe.
GetSequenceNo
func (e *BaseEntity) GetSequenceNo() int
Returns the last event sequence number. Thread-safe.
GetUncommittedEvents
func (e *BaseEntity) GetUncommittedEvents() []domain.EventEnvelope[any]
Returns a copy of all uncommitted events. Thread-safe.
ClearUncommittedEvents
func (e *BaseEntity) ClearUncommittedEvents()
Removes all uncommitted events. Typically called after successful persistence. Thread-safe.
ApplyEvent
func (e *BaseEntity) ApplyEvent(ctx context.Context, event domain.EventEnvelope[any]) error
Applies a stored event to the entity during replay. Validates the event belongs to this aggregate, checks for duplicate application (idempotency), verifies the sequence number is consecutive, and advances the sequence number.
Returns ErrWrongAggregate, ErrDuplicateEvent, or ErrInvalidEventSequenceNo on validation failure.
RecordEvent
func (e *BaseEntity) RecordEvent(payload any, eventType string) error
Records a new domain event. Creates an EventEnvelope internally with the next sequence number, marks the event as applied, and adds it to the uncommitted events list.
Package domain
import "github.com/akeemphilbert/pericarp/pkg/eventsourcing/domain"
Core interfaces and types for event sourcing.
Types
Event (interface)
type Event interface {
GetID() string
GetSequenceNo() int
}
Optional interface that event payloads can implement. When a payload implements Event, NewEventEnvelope extracts the aggregate ID from GetID() instead of using the aggregateID parameter.
EventEnvelope[T]
type EventEnvelope[T any] struct {
ID string `json:"id"`
AggregateID string `json:"aggregate_id"`
EventType string `json:"event_type"`
Payload T `json:"payload"`
Created time.Time `json:"timestamp"`
SequenceNo int `json:"sequence_no"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
Generic wrapper around event payloads with metadata for transport and persistence. Implements json.Marshaler and json.Unmarshaler.
BasicTripleEvent
type BasicTripleEvent struct {
Subject string `json:"subject"`
Predicate string `json:"predicate"`
Object string `json:"object"`
Original int64 `json:"original"`
}
Standard RDF-style subject-predicate-object event payload for modeling relationships.
Entity (interface)
type Entity interface {
GetID() string
GetSequenceNo() int
GetUncommittedEvents() []EventEnvelope[any]
ClearUncommittedEvents()
}
Interface for entities tracked by UnitOfWork. ddd.BaseEntity satisfies this interface.
EventStore (interface)
type EventStore interface {
Append(ctx context.Context, aggregateID string, expectedVersion int, events ...EventEnvelope[any]) error
GetEvents(ctx context.Context, aggregateID string) ([]EventEnvelope[any], error)
GetEventsFromVersion(ctx context.Context, aggregateID string, fromVersion int) ([]EventEnvelope[any], error)
GetEventsRange(ctx context.Context, aggregateID string, fromVersion, toVersion int) ([]EventEnvelope[any], error)
GetEventByID(ctx context.Context, eventID string) (EventEnvelope[any], error)
GetCurrentVersion(ctx context.Context, aggregateID string) (int, error)
Close() error
}
Interface for event persistence. Implementations must be thread-safe.
Append — Appends events with optimistic concurrency control. Pass expectedVersion = -1 to skip the version check.
GetEventsRange — Pass -1 for fromVersion to start from the beginning. Pass -1 for toVersion to read to the end.
GetCurrentVersion — Returns 0 if the aggregate doesn’t exist.
EventHandler[T]
type EventHandler[T any] func(ctx context.Context, env EventEnvelope[T]) error
Type-safe handler function for processing events.
EventDispatcher
type EventDispatcher struct { /* unexported fields */ }
Registers event handlers and dispatches events to them using dot-separated pattern matching. Handlers execute in parallel via errgroup.
Sentinel Errors
var ErrEventNotFound = errors.New("event not found")
var ErrConcurrencyConflict = errors.New("concurrency conflict: expected version mismatch")
var ErrInvalidEvent = errors.New("invalid event")
Event Type Constants
const EventTypeCreate = "created"
const EventTypeUpdate = "updated"
const EventTypeDelete = "deleted"
const EventTypeTriple = "triple"
Functions
NewEventEnvelope[T]
func NewEventEnvelope[T any](payload T, aggregateID, eventType string, sequenceNo int) EventEnvelope[T]
Creates a new EventEnvelope. Generates a KSUID for the ID. If payload implements the Event interface, aggregateID is extracted from payload.GetID() instead of using the parameter.
ToAnyEnvelope[T]
func ToAnyEnvelope[T any](envelope EventEnvelope[T]) EventEnvelope[any]
Converts a typed EventEnvelope[T] to EventEnvelope[any] for storage in an EventStore.
EventTypeFor
func EventTypeFor(entityType, action string) string
Constructs a dot-separated event type string. EventTypeFor("user", "created") returns "user.created".
IsStandardEventType
func IsStandardEventType(eventType string) bool
Returns true if the event type is one of the four standard constants.
NewEventDispatcher
func NewEventDispatcher() *EventDispatcher
Creates a new EventDispatcher.
Subscribe[T]
func Subscribe[T any](d *EventDispatcher, eventType string, handler EventHandler[T]) error
Registers a typed event handler for a specific event type pattern. The handler is wrapped to perform type assertion from EventEnvelope[any] to EventEnvelope[T]. Also registers a type factory for deserialization support.
Returns an error if eventType is empty or handler is nil.
SubscribeWildcard (method)
func (d *EventDispatcher) SubscribeWildcard(handler func(context.Context, EventEnvelope[any]) error) error
Registers a catch-all handler invoked for every dispatched event.
Dispatch (method)
func (d *EventDispatcher) Dispatch(ctx context.Context, envelope EventEnvelope[any]) error
Dispatches an event to all matching handlers. Pattern matching resolves exact, entity wildcard (user.*), action wildcard (*.created), full wildcard (*.*), and registered wildcard handlers. All handlers run in parallel. Returns a combined error if any handler fails.
RegisterType[T]
func RegisterType[T any](d *EventDispatcher, eventType string, factory func() T) error
Registers a type factory for deserialization. Used when unmarshaling events from storage.
UnmarshalEvent (method)
func (d *EventDispatcher) UnmarshalEvent(ctx context.Context, data []byte, eventType string) (EventEnvelope[any], error)
Unmarshals a JSON event using the type factory registered for eventType.
WrapEvent[T]
func WrapEvent[T any](payload T, aggregateID, eventType string, sequenceNo int) (EventEnvelope[T], error)
Wraps a typed payload in an EventEnvelope. Extracts AggregateID from payload if it implements Event.
MarshalEventToJSON[T]
func MarshalEventToJSON[T any](envelope EventEnvelope[T]) ([]byte, error)
Marshals a typed EventEnvelope to JSON bytes.
UnmarshalEventFromJSON[T]
func UnmarshalEventFromJSON[T any](data []byte) (EventEnvelope[T], error)
Unmarshals JSON bytes into a typed EventEnvelope[T].
Package infrastructure
import "github.com/akeemphilbert/pericarp/pkg/eventsourcing/infrastructure"
Concrete EventStore implementations.
MemoryStore
type MemoryStore struct { /* unexported fields */ }
In-memory event store. Thread-safe. Data is lost when the process exits. Ideal for unit tests.
func NewMemoryStore() *MemoryStore
Creates a new in-memory store.
All EventStore interface methods are implemented, plus:
func (m *MemoryStore) GetAllAggregateIDs() []string
Returns all aggregate IDs in the store. Useful for testing.
FileStore
type FileStore struct { /* unexported fields */ }
File-based event store. Stores events as JSON, one file per aggregate. Uses an in-memory cache for reads. Writes are atomic (temp file + rename). Thread-safe.
func NewFileStore(baseDir string) (*FileStore, error)
Creates a new file store at the given directory path. Creates the directory if it doesn’t exist. Loads all existing events from disk into memory on startup.
All EventStore interface methods are implemented.
Package application
import "github.com/akeemphilbert/pericarp/pkg/eventsourcing/application"
Application-level services.
Types
UnitOfWork (interface)
type UnitOfWork interface {
Track(entities ...domain.Entity) error
Commit(ctx context.Context) error
Rollback() error
}
Manages atomic persistence of uncommitted events from multiple tracked entities.
SimpleUnitOfWork
type SimpleUnitOfWork struct { /* unexported fields */ }
Default UnitOfWork implementation with optimistic concurrency control and optional event dispatch.
Functions
NewSimpleUnitOfWork
func NewSimpleUnitOfWork(eventStore domain.EventStore, dispatcher *domain.EventDispatcher) *SimpleUnitOfWork
Creates a new unit of work. dispatcher is optional — pass nil if event dispatch isn’t needed.
Methods on SimpleUnitOfWork
Track
func (uow *SimpleUnitOfWork) Track(entities ...domain.Entity) error
Registers entities for the next commit. Validates all entities before tracking any (nil check, non-empty ID, no duplicates). Captures each entity’s current sequence number as the expected version for optimistic concurrency.
Commit
func (uow *SimpleUnitOfWork) Commit(ctx context.Context) error
Persists all uncommitted events from tracked entities. For each aggregate, calls EventStore.Append with the expected version captured at Track time. On success, clears uncommitted events and tracking. If a dispatcher was provided, dispatches all persisted events (dispatch errors are non-fatal). On persistence failure, calls Rollback.
Rollback
func (uow *SimpleUnitOfWork) Rollback() error
Clears entity tracking without clearing uncommitted events. The entities can be re-tracked in a new unit of work for retry.
Package cqrs
import "github.com/akeemphilbert/pericarp/pkg/cqrs"
Command dispatching with typed receivers and observable results.
Types
CommandEnvelope[T]
type CommandEnvelope[T any] struct {
ID string `json:"id"`
CommandType string `json:"command_type"`
Payload T `json:"payload"`
Created time.Time `json:"timestamp"`
Metadata map[string]any `json:"metadata,omitempty"`
}
Generic wrapper around command payloads with metadata.
CommandReceiver[T]
type CommandReceiver[T any] func(ctx context.Context, env CommandEnvelope[T]) (any, error)
Type-safe receiver function for processing commands.
CommandResult
type CommandResult struct {
Value any
Error error
CommandType string
}
Result from a single receiver execution. When a receiver returns an error, Value is nil. When a receiver panics, the panic is recovered and wrapped as an Error.
Watchable
type Watchable struct { /* unexported fields */ }
Allows the caller to observe results incrementally as receivers complete. The internal results channel is buffered to the number of matched receivers so goroutines never block on send.
CommandDispatcher (interface)
type CommandDispatcher interface {
Dispatch(ctx context.Context, envelope CommandEnvelope[any]) *Watchable
RegisterWildcardReceiver(receiver func(context.Context, CommandEnvelope[any]) (any, error)) error
Close() error
}
Common interface for both async and queued dispatchers.
AsyncCommandDispatcher
type AsyncCommandDispatcher struct { /* unexported fields */ }
Executes all matched receivers concurrently using goroutines. Results are sent to the Watchable as each receiver completes. All receivers run to completion regardless of individual errors.
QueuedCommandDispatcher
type QueuedCommandDispatcher struct { /* unexported fields */ }
Executes matched receivers sequentially in registration order. Each result is sent to the Watchable before the next receiver is invoked. If the context is cancelled between receivers, subsequent receivers are skipped.
Functions
NewCommandEnvelope[T]
func NewCommandEnvelope[T any](payload T, commandType string) CommandEnvelope[T]
Creates a new CommandEnvelope with a KSUID, current timestamp, and empty metadata map.
ToAnyCommandEnvelope[T]
func ToAnyCommandEnvelope[T any](env CommandEnvelope[T]) CommandEnvelope[any]
Converts a typed CommandEnvelope[T] to CommandEnvelope[any] for dispatch.
NewAsyncCommandDispatcher
func NewAsyncCommandDispatcher() *AsyncCommandDispatcher
NewQueuedCommandDispatcher
func NewQueuedCommandDispatcher() *QueuedCommandDispatcher
RegisterReceiver[T]
func RegisterReceiver[T any](d CommandDispatcher, commandType string, receiver CommandReceiver[T]) error
Registers a typed receiver for a command type pattern. Supports the same dot-separated pattern matching as the EventDispatcher (user.create, user.*, *.create, *.*). Multiple receivers can be registered for the same pattern.
Returns an error if commandType is empty, receiver is nil, or the dispatcher doesn’t support registration.
Methods on Watchable
Results
func (w *Watchable) Results() <-chan CommandResult
Returns a receive-only channel of results. The channel is closed after all receivers complete.
Wait
func (w *Watchable) Wait() []CommandResult
Blocks until all receivers complete and returns all results as a slice.
First
func (w *Watchable) First() (CommandResult, bool)
Blocks until the first result arrives. Returns (result, true) on success. Returns (zero, false) if no receivers were registered. Remaining receivers continue in the background.
Done
func (w *Watchable) Done() <-chan struct{}
Returns a channel that is closed when all receivers have completed. Use in select statements.
Methods on dispatchers
Dispatch
func (d *AsyncCommandDispatcher) Dispatch(ctx context.Context, envelope CommandEnvelope[any]) *Watchable
func (d *QueuedCommandDispatcher) Dispatch(ctx context.Context, envelope CommandEnvelope[any]) *Watchable
Resolves matching receivers and executes them. Returns a Watchable for observing results. If no receivers match, the returned Watchable is immediately complete with zero results.
RegisterWildcardReceiver
func (d *AsyncCommandDispatcher) RegisterWildcardReceiver(receiver func(context.Context, CommandEnvelope[any]) (any, error)) error
func (d *QueuedCommandDispatcher) RegisterWildcardReceiver(receiver func(context.Context, CommandEnvelope[any]) (any, error)) error
Registers a catch-all receiver invoked for all command types. Returns an error if receiver is nil.
Close
func (d *AsyncCommandDispatcher) Close() error
func (d *QueuedCommandDispatcher) Close() error
Releases resources. Currently a no-op for both implementations.
Package auth/domain/entities
import "github.com/akeemphilbert/pericarp/pkg/auth/domain/entities"
Domain aggregates for authentication, authorization, and identity management.
Aggregates
Agent
type Agent struct {
*ddd.BaseEntity
// unexported fields
}
Represents an authenticated party in the system. Uses FOAF vocabulary for agent typing.
Agent Types (constants):
| Constant | Value | Description |
|---|---|---|
AgentTypePerson | foaf:Person | Human user |
AgentTypeOrganization | foaf:Organization | Organizational entity |
AgentTypeGroup | foaf:Group | Group of agents |
AgentTypeSoftwareAgent | foaf:Agent | Automated software agent |
Methods:
With(id, name, agentType string) (*Agent, error)— Factory. Creates a new agent and recordsAgent.Created. Defaults tofoaf:PersonifagentTypeis empty.Restore(id, name, agentType string, active bool, createdAt time.Time) error— Restores from database without recording events.Activate() error/Deactivate() error— Idempotent state transitions.AssignRole(roleID string) error— RecordsAgent.RoleAssignedtriple event (agent,org:hasRole, role).RevokeRole(roleID string) error— RecordsAgent.RoleRevokedtriple event (agent,org:hadRole, role).AddToGroup(groupID string) error— RecordsAgent.GroupMembershipAdded(agent,foaf:member, group).RemoveFromGroup(groupID string) error— RecordsAgent.GroupMembershipRemoved.Name() string,AgentType() string,Active() bool,CreatedAt() time.Time— Accessors.
Credential
type Credential struct {
*ddd.BaseEntity
// unexported fields
}
Links an external identity provider account to an Agent. Uses Schema.org vocabulary.
Methods:
With(id, agentID, provider, providerUserID, email, displayName string) (*Credential, error)— Factory. Creates a new credential and recordsCredential.Createdtriple event (agent,schema:credential, credential).Restore(id, agentID, provider, providerUserID, email, displayName string, active bool, createdAt, lastUsedAt time.Time) error— Restores from database.MarkUsed() error— RecordsCredential.Used. UpdatesLastUsedAt.Deactivate() error/Reactivate() error— Idempotent state transitions.ApplyEvent(ctx context.Context, envelope domain.EventEnvelope[any]) error— Replays events for state reconstruction.AgentID() string,Provider() string,ProviderUserID() string,Email() string,DisplayName() string,Active() bool,CreatedAt() time.Time,LastUsedAt() time.Time— Accessors.
AuthSession
type AuthSession struct {
*ddd.BaseEntity
// unexported fields
}
Represents an authenticated session with expiration and optional account scoping.
Methods:
With(id, agentID, credentialID, ipAddress, userAgent string, expiresAt time.Time) (*AuthSession, error)— Factory. RecordsSession.Createdtriple event (agent,schema:session, session).Restore(id, agentID, accountID, credentialID, ipAddress, userAgent string, active bool, createdAt, expiresAt, lastAccessedAt time.Time) error— Restores from database.Touch() error— UpdatesLastAccessedAt. RecordsSession.Touched.Revoke() error— Idempotent. RecordsSession.Revoked.IsExpired() bool— Returns true if the current time is pastExpiresAt.ScopeToAccount(accountID string) error— RecordsSession.AccountScopedtriple event (session,schema:authenticator, account).ApplyEvent(ctx context.Context, envelope domain.EventEnvelope[any]) error— Replays events.AgentID() string,AccountID() string,CredentialID() string,Active() bool,CreatedAt() time.Time,ExpiresAt() time.Time,LastAccessedAt() time.Time,IPAddress() string,UserAgent() string— Accessors.
Account
type Account struct {
*ddd.BaseEntity
// unexported fields
}
Tenant/workspace container for multi-tenancy. Uses W3C ORG vocabulary.
Methods:
With(id, name string) (*Account, error)— Factory. RecordsAccount.Created.Activate() error/Deactivate() error— Idempotent.AddMember(agentID, roleID string) error— RecordsAccount.MemberAddedtriple event (account,org:hasMember, agent) with role metadata.RemoveMember(agentID string) error— RecordsAccount.MemberRemoved(account,org:hadMember, agent).ChangeMemberRole(agentID, newRoleID string) error— RecordsAccount.MemberRoleChanged.
Role
type Role struct {
*ddd.BaseEntity
// unexported fields
}
Named role with description. Aligned with W3C ORG ontology.
With(id, name, description string) (*Role, error)— Factory. RecordsRole.Created.Restore(id, name, description string, createdAt time.Time) error— Restores from database.
Policy
type Policy struct {
*ddd.BaseEntity
// unexported fields
}
ODRL (Open Digital Rights Language) policy for access control.
Policy Types (constants):
| Constant | Value | Description |
|---|---|---|
PolicyTypeSet | odrl:Set | General-purpose policy |
PolicyTypeOffer | odrl:Offer | Policy proposed by the assigner |
PolicyTypeAgreement | odrl:Agreement | Policy agreed upon by both parties |
Methods:
With(id, name, policyType string) (*Policy, error)— Factory. Defaults toodrl:Set.GrantPermission(assignee, action, target string) error— RecordsPolicy.PermissionGrantedtriple event.RevokePermission(assignee, action, target string) error— RecordsPolicy.PermissionRevoked.SetProhibition(assignee, action, target string) error— RecordsPolicy.ProhibitionSet.RevokeProhibition(assignee, action, target string) error— RecordsPolicy.ProhibitionRevoked.ImposeDuty(assignee, action, target string) error— RecordsPolicy.DutyImposed.DischargeDuty(assignee, action, target string) error— RecordsPolicy.DutyDischarged.Assign(assigneeID string) error— RecordsPolicy.Assigned.
Ontology Constants
ODRL Actions:
| Constant | Value |
|---|---|
ActionUse | odrl:use |
ActionRead | odrl:read |
ActionModify | odrl:modify |
ActionDelete | odrl:delete |
ActionExecute | odrl:execute |
ActionAggregate | odrl:aggregate |
ActionDistribute | odrl:distribute |
ActionTransfer | odrl:transfer |
Relationship Predicates:
| Constant | Value | Usage |
|---|---|---|
PredicateHasRole | org:hasRole | Agent currently holds a role |
PredicateHadRole | org:hadRole | Agent previously held a role (revocation) |
PredicateHasMember | org:hasMember | Account has a member agent |
PredicateHadMember | org:hadMember | Account had a member (removal) |
PredicateMember | foaf:member | Agent belongs to group |
PredicateCredential | schema:credential | Agent linked to credential |
PredicateSession | schema:session | Agent linked to session |
PredicateAuthenticator | schema:authenticator | Session scoped to account |
Event Type Constants
All event types follow the Aggregate.Action naming convention:
// Agent events
EventTypeAgentCreated, EventTypeAgentActivated, EventTypeAgentDeactivated
EventTypeAgentRoleAssigned, EventTypeAgentRoleRevoked
EventTypeAgentGroupMembershipAdded, EventTypeAgentGroupMembershipRemoved
// Credential events
EventTypeCredentialCreated, EventTypeCredentialUsed
EventTypeCredentialDeactivated, EventTypeCredentialReactivated
// Session events
EventTypeSessionCreated, EventTypeSessionTouched
EventTypeSessionRevoked, EventTypeSessionAccountScoped
// Account events
EventTypeAccountCreated, EventTypeAccountActivated, EventTypeAccountDeactivated
EventTypeAccountMemberAdded, EventTypeAccountMemberRemoved, EventTypeAccountMemberRoleChanged
// Policy events
EventTypePolicyCreated, EventTypePolicyActivated, EventTypePolicyDeactivated
EventTypePermissionGranted, EventTypePermissionRevoked
EventTypeProhibitionSet, EventTypeProhibitionRevoked
EventTypeDutyImposed, EventTypeDutyDischarged, EventTypePolicyAssigned
// Role events
EventTypeRoleCreated
Package auth/domain/repositories
import "github.com/akeemphilbert/pericarp/pkg/auth/domain/repositories"
Repository interfaces for auth aggregate persistence. All support cursor-based pagination.
Types
PaginatedResponse[T]
type PaginatedResponse[T any] struct {
Data []T
Cursor string
Limit int
HasMore bool
}
AgentRepository (interface)
Save(ctx context.Context, agent *entities.Agent) error
FindByID(ctx context.Context, id string) (*entities.Agent, error)
FindAll(ctx context.Context, cursor string, limit int) (*PaginatedResponse[*entities.Agent], error)
CredentialRepository (interface)
Save(ctx context.Context, credential *entities.Credential) error
FindByID(ctx context.Context, id string) (*entities.Credential, error)
FindByProvider(ctx context.Context, provider, providerUserID string) (*entities.Credential, error)
FindByAgent(ctx context.Context, agentID string) ([]*entities.Credential, error)
FindAll(ctx context.Context, cursor string, limit int) (*PaginatedResponse[*entities.Credential], error)
AuthSessionRepository (interface)
Save(ctx context.Context, session *entities.AuthSession) error
FindByID(ctx context.Context, id string) (*entities.AuthSession, error)
FindByAgent(ctx context.Context, agentID string) ([]*entities.AuthSession, error)
FindActive(ctx context.Context, agentID string) ([]*entities.AuthSession, error)
FindAll(ctx context.Context, cursor string, limit int) (*PaginatedResponse[*entities.AuthSession], error)
RevokeAllForAgent(ctx context.Context, agentID string) error
AccountRepository, RoleRepository, PolicyRepository (interfaces)
Follow the same pattern with Save, FindByID, FindAll, plus domain-specific finders.
Package auth/application
import "github.com/akeemphilbert/pericarp/pkg/auth/application"
Application services for authentication and authorization.
Types
AuthRequest
type AuthRequest struct {
AuthURL string // Authorization URL to redirect the user to
State string // CSRF protection parameter
CodeVerifier string // PKCE code verifier (store server-side)
Nonce string // OpenID Connect nonce
Provider string // Provider name
}
AuthResult
type AuthResult struct {
AccessToken string
RefreshToken string
IDToken string
TokenType string
ExpiresIn int // seconds
UserInfo UserInfo
}
UserInfo
type UserInfo struct {
ProviderUserID string // User's ID at the provider
Email string
DisplayName string
AvatarURL string
Provider string // Provider name (e.g., "google")
}
SessionInfo
type SessionInfo struct {
SessionID string
AgentID string
AccountID string // Empty if not scoped
Permissions []Permission
ExpiresAt time.Time
}
Permission
type Permission struct {
Assignee string // Agent or Role ID
Action string // ODRL action IRI
Target string // Resource identifier or "*" wildcard
}
Interfaces
AuthenticationService
type AuthenticationService interface {
InitiateAuthFlow(ctx context.Context, provider, redirectURI string) (*AuthRequest, error)
ExchangeCode(ctx context.Context, code, codeVerifier, provider, redirectURI string) (*AuthResult, error)
ValidateState(ctx context.Context, receivedState, storedState string) error
FindOrCreateAgent(ctx context.Context, userInfo UserInfo) (*entities.Agent, *entities.Credential, error)
CreateSession(ctx context.Context, agentID, credentialID, ipAddress, userAgent string, duration time.Duration) (*entities.AuthSession, error)
ValidateSession(ctx context.Context, sessionID string) (*SessionInfo, error)
RefreshTokens(ctx context.Context, credentialID string) (*AuthResult, error)
RevokeSession(ctx context.Context, sessionID string) error
RevokeAllSessions(ctx context.Context, agentID string) error
}
OAuthProvider
type OAuthProvider interface {
Name() string
AuthCodeURL(state, codeChallenge, nonce, redirectURI string) string
Exchange(ctx context.Context, code, codeVerifier, redirectURI string) (*AuthResult, error)
RefreshToken(ctx context.Context, refreshToken string) (*AuthResult, error)
RevokeToken(ctx context.Context, token string) error
ValidateIDToken(ctx context.Context, idToken, nonce string) (*UserInfo, error)
}
Provider-agnostic interface for OAuth 2.0 / OpenID Connect identity providers. Implement once per provider (Google, GitHub, etc.).
OAuthProviderRegistry
type OAuthProviderRegistry map[string]OAuthProvider
Maps provider names to their implementations.
TokenStore
type TokenStore interface {
StoreTokens(ctx context.Context, credentialID string, accessToken, refreshToken, idToken string, expiresAt time.Time) error
GetTokens(ctx context.Context, credentialID string) (accessToken, refreshToken string, expiresAt time.Time, err error)
DeleteTokens(ctx context.Context, credentialID string) error
NeedsRefresh(ctx context.Context, credentialID string) (bool, error)
}
Abstraction for encrypted server-side token storage. Consuming applications implement this against their storage layer.
AuthorizationChecker
type AuthorizationChecker interface {
IsAuthorized(ctx context.Context, agentID, action, target string) (bool, error)
IsAuthorizedInAccount(ctx context.Context, agentID, accountID, action, target string) (bool, error)
GetPermissions(ctx context.Context, agentID string) ([]Permission, error)
GetProhibitions(ctx context.Context, agentID string) ([]Permission, error)
}
PermissionStore
type PermissionStore interface {
GetPermissionsForAssignee(ctx context.Context, assigneeID string) ([]Permission, error)
GetProhibitionsForAssignee(ctx context.Context, assigneeID string) ([]Permission, error)
GetRolesForAgent(ctx context.Context, agentID string) ([]string, error)
GetRolesForAgentInAccount(ctx context.Context, agentID, accountID string) ([]string, error)
}
Read model for authorization decisions. Consuming applications implement this against their storage layer.
Implementations
DefaultAuthenticationService
func NewDefaultAuthenticationService(
providers OAuthProviderRegistry,
agents repositories.AgentRepository,
credentials repositories.CredentialRepository,
sessions repositories.AuthSessionRepository,
tokens TokenStore,
authorization AuthorizationChecker,
) *DefaultAuthenticationService
Reference implementation of AuthenticationService. The authorization parameter is optional (pass nil to skip permission resolution in ValidateSession).
PolicyDecisionPoint
func NewPolicyDecisionPoint(store PermissionStore) *PolicyDecisionPoint
Implements AuthorizationChecker using ODRL semantics: prohibitions override permissions, default deny.
PKCE Functions
func GenerateCodeVerifier() (string, error)
Generates a 43-character base64url-encoded code verifier from 32 random bytes (RFC 7636).
func GenerateCodeChallenge(verifier string) string
Generates a S256 code challenge (SHA-256 + base64url) from the code verifier.
func GenerateState() (string, error)
Generates a 32-byte base64url-encoded state parameter for CSRF protection.
func GenerateNonce() (string, error)
Generates a 32-byte base64url-encoded nonce for OpenID Connect ID token validation.
Sentinel Errors
var ErrInvalidProvider = errors.New("authentication: invalid provider")
var ErrInvalidState = errors.New("authentication: invalid state parameter")
var ErrCodeExchangeFailed = errors.New("authentication: code exchange failed")
var ErrSessionNotFound = errors.New("authentication: session not found")
var ErrSessionExpired = errors.New("authentication: session expired")
var ErrSessionRevoked = errors.New("authentication: session revoked")
var ErrTokenRefreshFailed = errors.New("authentication: token refresh failed")
var ErrCredentialNotFound = errors.New("authentication: credential not found")
Package auth/infrastructure/session
import "github.com/akeemphilbert/pericarp/pkg/auth/infrastructure/session"
HTTP session management for the BFF (Backend-for-Frontend) pattern.
Types
SessionData
type SessionData struct {
SessionID string
AgentID string
AccountID string // Empty if not scoped
CreatedAt time.Time
ExpiresAt time.Time
}
FlowData
type FlowData struct {
State string
CodeVerifier string
Nonce string
Provider string
RedirectURI string
CreatedAt time.Time
}
Temporary OAuth flow data stored server-side during the authorization code exchange. Automatically cleared after retrieval (one-time use). Maximum TTL: 10 minutes.
SessionOptions
type SessionOptions struct {
MaxAge int // seconds (default: 86400)
Domain string
Path string // default: "/"
HttpOnly bool // default: true
Secure bool // default: true
SameSite http.SameSite // default: Lax
}
Interface
SessionManager
type SessionManager interface {
CreateHTTPSession(w http.ResponseWriter, r *http.Request, sessionInfo SessionData) error
GetHTTPSession(r *http.Request) (*SessionData, error)
DestroyHTTPSession(w http.ResponseWriter, r *http.Request) error
SetFlowData(w http.ResponseWriter, r *http.Request, data FlowData) error
GetFlowData(w http.ResponseWriter, r *http.Request) (*FlowData, error)
}
Functions
DefaultSessionOptions
func DefaultSessionOptions() SessionOptions
Returns secure defaults: HttpOnly=true, Secure=true, SameSite=Lax, MaxAge=86400 (24 hours), Path="/".
NewGorillaSessionManager
func NewGorillaSessionManager(sessionName string, store sessions.Store, options SessionOptions) *GorillaSessionManager
Creates a session manager backed by gorilla/sessions. The store parameter accepts any gorilla session store (cookie, filesystem, Redis, database).
Sentinel Errors
var ErrSessionNotFound = errors.New("session: not found")
var ErrFlowDataNotFound = errors.New("session: flow data not found")
Package auth/infrastructure/casbin
import "github.com/akeemphilbert/pericarp/pkg/auth/infrastructure/casbin"
Casbin-backed implementation of AuthorizationChecker with RBAC, domain support, and deny-override policy effects.
Types
CasbinAuthorizationChecker
type CasbinAuthorizationChecker struct { /* unexported fields */ }
Implements application.AuthorizationChecker using Casbin’s enforcement engine. Uses an embedded RBAC model that maps ODRL semantics to Casbin policies with domain-based scoping for multi-tenancy.
Constructors
NewCasbinAuthorizationChecker
func NewCasbinAuthorizationChecker(adapter persist.Adapter) (*CasbinAuthorizationChecker, error)
Creates a checker with the embedded ODRL-compatible Casbin model and the given adapter for policy persistence. The adapter parameter accepts any github.com/casbin/casbin/v3/persist.Adapter implementation (GORM, file, PostgreSQL, etc.). Pass nil for in-memory only (no persistence).
Internally, the constructor:
- Loads the embedded RBAC model with deny-override policy effects
- Creates a Casbin enforcer with the model and adapter
- Registers a domain matching function so that
"*"policies match any request domain
NewCasbinAuthorizationCheckerFromEnforcer
func NewCasbinAuthorizationCheckerFromEnforcer(enforcer *casbin.Enforcer) *CasbinAuthorizationChecker
Wraps a pre-configured Casbin enforcer. The caller is responsible for configuring the model, adapter, and domain matching function. Use this when you need a custom Casbin model or non-standard configuration.
AuthorizationChecker Methods
These methods implement the application.AuthorizationChecker interface.
IsAuthorized
func (c *CasbinAuthorizationChecker) IsAuthorized(ctx context.Context, agentID, action, target string) (bool, error)
Checks whether the agent is authorized using only global roles and policies. Calls Enforce(agentID, "*", action, target) internally.
IsAuthorizedInAccount
func (c *CasbinAuthorizationChecker) IsAuthorizedInAccount(ctx context.Context, agentID, accountID, action, target string) (bool, error)
Checks whether the agent is authorized within an account context. Both global and account-scoped roles are considered via the domain matching function. Calls Enforce(agentID, accountID, action, target) internally.
GetPermissions
func (c *CasbinAuthorizationChecker) GetPermissions(ctx context.Context, agentID string) ([]application.Permission, error)
Returns all effective permissions (eft=allow) for the agent, including permissions inherited through global role assignments.
GetProhibitions
func (c *CasbinAuthorizationChecker) GetProhibitions(ctx context.Context, agentID string) ([]application.Permission, error)
Returns all effective prohibitions (eft=deny) for the agent, including prohibitions inherited through global role assignments.
Convenience Methods
These methods manage Casbin policies and grouping policies directly.
AddPermission
func (c *CasbinAuthorizationChecker) AddPermission(assignee, action, target string) error
Adds a global permission. Creates a Casbin policy: (assignee, "*", action, target, "allow").
AddProhibition
func (c *CasbinAuthorizationChecker) AddProhibition(assignee, action, target string) error
Adds a global prohibition. Creates a Casbin policy: (assignee, "*", action, target, "deny").
RemovePermission
func (c *CasbinAuthorizationChecker) RemovePermission(assignee, action, target string) error
Removes a global permission policy.
RemoveProhibition
func (c *CasbinAuthorizationChecker) RemoveProhibition(assignee, action, target string) error
Removes a global prohibition policy.
AssignRole
func (c *CasbinAuthorizationChecker) AssignRole(agentID, roleID string) error
Assigns a global role to an agent. Creates a Casbin grouping policy: (agentID, roleID, "*"). Global roles apply in all domain contexts.
AssignAccountRole
func (c *CasbinAuthorizationChecker) AssignAccountRole(agentID, roleID, accountID string) error
Assigns a role to an agent within a specific account. Creates a Casbin grouping policy: (agentID, roleID, accountID). Account-scoped roles only apply when checking authorization in that account.
RevokeRole
func (c *CasbinAuthorizationChecker) RevokeRole(agentID, roleID string) error
Removes a global role assignment.
RevokeAccountRole
func (c *CasbinAuthorizationChecker) RevokeAccountRole(agentID, roleID, accountID string) error
Removes an account-scoped role assignment.
Embedded Casbin Model
The checker uses the following embedded model:
[request_definition]
r = sub, dom, act, obj
[policy_definition]
p = sub, dom, act, obj, eft
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.act == p.act && (r.obj == p.obj || p.obj == "*") && (r.dom == p.dom || p.dom == "*")