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

interfaces

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package interfaces defines the core contracts for the Tech AI Knowledge Engine. All services program against these interfaces. Implementations are provided by the foundation and platform layers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InTx

func InTx(ctx context.Context) bool

InTx reports whether ctx is executing inside a transaction opened by a TransactionManager. Statement-level retry must be skipped inside a transaction: once any statement fails the transaction is aborted and every subsequent command fails until it ends, so retrying only masks the real error.

func RegisterAfterCommit

func RegisterAfterCommit(ctx context.Context, fn func())

RegisterAfterCommit schedules fn to run AFTER the enclosing transaction commits successfully. If ctx is not inside a transaction (no registry installed), fn runs immediately — so a non-transactional caller still sees the side effect. This lets a side effect that must not become visible until the write is durable — e.g. rotating a cache generation token, so a concurrent reader cannot cache a pre-commit snapshot under the new generation — be scheduled uniformly whether or not the write is wrapped in a transaction.

func RunAfterCommit

func RunAfterCommit(ctx context.Context)

RunAfterCommit runs every hook registered on ctx in registration order, then clears them. A TransactionManager calls this exactly once after a successful commit. It is a no-op when no registry is present.

func WithAfterCommit

func WithAfterCommit(ctx context.Context) context.Context

WithAfterCommit returns a context carrying a fresh after-commit registry. A TransactionManager installs it when it opens a transaction, then calls RunAfterCommit once the commit succeeds. Code running inside the transaction schedules post-commit side effects via RegisterAfterCommit.

func WithInTx

func WithInTx(ctx context.Context) context.Context

WithInTx returns a context marked as executing inside a transaction. A TransactionManager sets this when it opens a transaction so that generic infrastructure (e.g. the repository retry decorator) can detect it without depending on any concrete ORM/transaction implementation.

Types

type AdaptiveThrottler

type AdaptiveThrottler interface {
	// Do runs op unless the throttler decides to reject it locally, in which case it returns a
	// coded "throttled" rejection (see adaptivethrottle) WITHOUT invoking op. Each op outcome
	// (accepted/rejected by the backend) feeds the accept-rate window that drives the decision.
	Do(ctx context.Context, op func() error) error
}

AdaptiveThrottler applies client-side adaptive throttling (Google SRE "Handling Overload"): it tracks the recent accept/reject ratio and, when a backend starts failing, rejects a growing fraction of outbound requests locally — shedding load before it reaches the struggling backend and easing off as the backend recovers. It composes with the retry budget: a locally-throttled request is a terminal rejection, not retried.

A peer resilience contract of Retrier and Bulkhead: it wraps an operation with a policy and depends on no concrete implementation. Swap implementations via the adaptivethrottle.New factory.

type AnyJob

type AnyJob interface {
	// Named supplies the job's identity (Meta) for display and tool resolution.
	Named

	// Execute runs the job against runner (rooted at root) and returns collected
	// step results.
	Execute(
		ctx context.Context,
		runner CommandRunner,
		root string,
	) (types.StepResults, error)
}

AnyJob is a command-oriented execution unit: it runs against a CommandRunner rooted at a filesystem path. The generic Job[T] in the execution module and the CLI's command jobs implement it; the engine and gate operate on this type.

type BufferedMessage

type BufferedMessage struct {
	// ID is the message's per-connection monotonic id (e.g. "msg-7").
	ID string
	// Payload is the marshaled frame to resend unchanged (encoding-independent bytes).
	Payload []byte
}

BufferedMessage is one retained outbound frame in a ReplayBuffer: its per-connection monotonic message id and the opaque marshaled payload to resend verbatim on reconnect.

type Bulkhead

type Bulkhead interface {
	// Execute runs fn within the concurrency limit. Blocks until a slot is
	// available or ctx is cancelled.
	Execute(ctx context.Context, fn func() error) error

	// TryExecute attempts to run fn without blocking. Returns an error if no
	// slots are available.
	TryExecute(fn func() error) error
}

Bulkhead limits concurrent access to a resource, preventing unbounded parallelism from exhausting goroutines, file descriptors, and memory.

Implementations: channel semaphore (default).

All service code depends on this interface, never on a concrete implementation. Swap implementations via the bulkhead.New factory.

type ByteCache

type ByteCache interface {
	// Get retrieves a value by key. Returns (nil, false) if not found or expired.
	Get(ctx context.Context, key string) ([]byte, bool)

	// Set stores a value with the given TTL.
	Set(ctx context.Context, key string, value []byte, ttl time.Duration)

	// Delete removes a value by key.
	Delete(ctx context.Context, key string)
}

ByteCache is the non-generic cache interface for raw byte storage. Used by infrastructure wrappers (metrics, tracing) that operate on serialized values without caring about the concrete type.

Compared to the generic Cache[T], ByteCache:

  • Uses []byte values instead of typed values
  • Returns ([]byte, bool) from Get instead of (T, bool)
  • Set does not return an error (fire-and-forget)
  • Has no Exists method

type Cache

type Cache[T any] interface {
	// Get retrieves a value by key. Returns (zero-value, false) if not found or expired.
	Get(ctx context.Context, key string) (T, bool)

	// Set stores a value with the given TTL. TTL of 0 means no expiration.
	Set(ctx context.Context, key string, value T, ttl time.Duration) error

	// Delete removes a value by key.
	Delete(ctx context.Context, key string) error

	// Exists checks if a key exists and is not expired.
	Exists(ctx context.Context, key string) bool
}

Cache is the generic caching interface. T = value type.

type CacheInvalidator

type CacheInvalidator interface {
	// InvalidatePrefix removes all keys matching the given prefix.
	InvalidatePrefix(ctx context.Context, prefix string) error

	// InvalidateAll clears the entire cache.
	InvalidateAll(ctx context.Context) error
}

CacheInvalidator provides cache invalidation capabilities.

type CircuitBreaker

type CircuitBreaker interface {
	// Execute runs fn through the circuit breaker. Returns an error if the
	// circuit is open or fn fails.
	Execute(fn func() error) error
}

CircuitBreaker prevents cascading failures by detecting downstream failures and shedding load by failing fast without invoking the downstream.

Implementations: gobreaker (default).

All service code depends on this interface, never on a concrete circuit breaker library. Swap implementations via the circuitbreaker.New factory.

type Client

type Client interface {
	// Lifecycle provides coordinated Start/Stop.
	Lifecycle
	// HealthChecker provides the Kubernetes Liveness/Readiness probes.
	HealthChecker
}

Client is the base contract for a resilient, lifecycle-managed client (gRPC, database pool, Connect server): it can be started/stopped by a lifecycle.Manager and health-probed by Kubernetes. It composes Lifecycle + HealthChecker.

type CmdHook

type CmdHook interface {
	// OnStart is called immediately before a command is launched.
	// cmdStr is the full command string (name + joined args).
	OnStart(cmdStr string)

	// OnSuccess is called when a command exits with a zero exit code.
	OnSuccess(cmdStr string)

	// OnFailure is called when a command exits with a non-zero exit code.
	OnFailure(cmdStr string)
}

CmdHook receives lifecycle callbacks for each command executed by a CommandRunner. Implementations must not block; the callbacks fire synchronously from the runner goroutine.

type CmdResult

type CmdResult struct {
	// Err is the error returned by the command, or nil on success.
	Err error

	// Stdout is the captured standard output.
	Stdout []byte

	// Stderr is the captured standard error.
	Stderr []byte

	// Duration is the wall-clock time the command took to execute.
	Duration time.Duration

	// ExitCode is the process exit code: 0 on success, the process's code on a
	// non-zero exit, or -1 when the command could not be started or did not exit
	// normally. Lets callers branch on specific codes (e.g. pytest's 5 = "no
	// tests collected") without parsing Err.
	ExitCode int
}

CmdResult holds the captured output of a buffered command execution.

type CommandRunner

type CommandRunner interface {
	// Run executes a command with inherited stdout/stderr.
	Run(ctx context.Context, dir, name string, args ...string) error

	// RunWithEnv executes a command with additional environment variables.
	RunWithEnv(
		ctx context.Context,
		dir string,
		extraEnv []string,
		name string,
		args ...string,
	) error

	// RunBuffered executes a command with captured stdout/stderr.
	RunBuffered(ctx context.Context, dir, name string, args ...string) CmdResult

	// RunBufferedWithEnv executes a command with additional environment
	// variables and captured stdout/stderr.
	RunBufferedWithEnv(
		ctx context.Context,
		dir string,
		extraEnv []string,
		name string,
		args ...string,
	) CmdResult

	// Exists checks whether a binary is available on PATH.
	Exists(name string) bool

	// RequireTool returns an error if the named binary is not on PATH.
	RequireTool(name, installHint string) error
}

CommandRunner executes external CLI commands. The canonical definition lives here (L0) so the execution engine can depend on it without importing foundation. foundation/system aliases this type so existing consumers are unchanged.

type CompletedPart

type CompletedPart struct {
	// ETag is the entity tag S3 returned from the part's UploadPart request.
	ETag string `json:"etag"`
	// PartNumber is the part's 1-based index within the multipart upload.
	PartNumber int32 `json:"part_number"`
}

CompletedPart identifies one finished part of a multipart upload, pairing its 1-based part number with the ETag S3 returned from the UploadPart request.

type ConfigLoader

type ConfigLoader interface {
	// Get returns the raw value for a key, or nil if not set.
	Get(key string) any

	// GetString returns the string value for a key.
	GetString(key string) string

	// GetInt returns the int value for a key.
	GetInt(key string) int

	// GetBool returns the bool value for a key.
	GetBool(key string) bool

	// Unmarshal decodes the full configuration into the target struct.
	Unmarshal(target any) error

	// UnmarshalKey decodes a specific configuration section into the target struct.
	// The key parameter is a dot-separated path (e.g., "database", "server.identity").
	UnmarshalKey(key string, target any) error
}

ConfigLoader provides read-only access to loaded configuration values. Implementations handle the loading mechanics (files, env vars, remote sources); callers interact only through this interface.

The interface deliberately omits a Load() method. Loading is a one-time setup concern handled by the concrete type's constructor or factory. Callers receive an already-loaded ConfigLoader ready for use.

type ConnectorKind

type ConnectorKind string

ConnectorKind selects a connector backend (the family discriminator), mirroring the storage tier's Kind pattern. S3 now; gdrive/salesforce/… are added as new kinds — a config change, not an edit to the builder's callers or a sync engine.

const ConnectorKindS3 ConnectorKind = "s3"

ConnectorKindS3 is the S3-compatible object-store connector (the first — and, for now, only — kind).

type ConnectorSource

type ConnectorSource interface {
	// TestConnection probes the source with the connector's credentials and returns a bounded count
	// of reachable items (e.g. objects under an S3 bucket/prefix), or a coded error when the source
	// is unreachable or the credentials are rejected.
	TestConnection(ctx context.Context) (int, error)

	// ListPage streams one page of the source's objects starting AFTER continuationToken (empty = the
	// start of the listing) and returns the page plus the token to resume from ("" when the listing is
	// exhausted). It is the resumable, memory-bounded primitive a sync process crawls a large source
	// with — holding one page at a time (never the full key list) and checkpointing the token so a
	// crashed sync resumes from the last committed page rather than restarting a million-object crawl.
	// A non-positive limit lets the backend apply its default page size. A failure is a coded error
	// (transient CodeUnavailable vs terminal CodeInvalidInput) so the caller can classify retry-vs-fail.
	ListPage(
		ctx context.Context,
		continuationToken string,
		limit int,
	) (objects []StorageObject, nextToken string, err error)
}

ConnectorSource is the connector-family read seam any connector type satisfies — an object store now (S3), a record system (Salesforce) or a file SaaS (GDrive/SharePoint) later. It is deliberately transport-agnostic and AWS-free so it lives in dependency-free core; the concrete S3/OAuth clients live in go/clients/connector/<type>. It exposes the two reads a connector needs: a bounded reachability probe (TestConnection, e.g. to validate newly entered credentials) and the resumable, memory-bounded page-by-page listing a sync process crawls (ListPage). It does NOT fetch or normalize object BODIES — parsing, embedding and ingesting them is the caller's pipeline.

type Counter

type Counter interface {
	// Inc increments the counter by 1 with the given label values.
	Inc(labelValues ...string)

	// Add increments the counter by the given value.
	Add(value float64, labelValues ...string)
}

Counter is an incrementing metric.

type CountingPublisher

type CountingPublisher interface {
	// MessagePublisher provides the base Publish/PublishBatch operations.
	MessagePublisher

	// PublishCount sends payload to topic and returns the number of subscribers that
	// received it (0 when no subscriber is listening). For Redis Pub/Sub this is the
	// PUBLISH reply; a positive count means the message was delivered to at least one
	// subscriber.
	PublishCount(ctx context.Context, topic string, payload []byte) (int, error)
}

CountingPublisher is a MessagePublisher that also reports how many subscribers received a published message — the Redis PUBLISH model, whose reply is the number of clients the message was delivered to. It is an opt-in capability interface that composes with MessagePublisher (ARCHITECTURE.md#interface-composition) rather than widening the shared contract: a fan-out consumer that must know whether a message reached anyone (e.g. the WebSocket backplane deciding cluster-wide delivery) depends on it, while a broker with no delivered-count notion (SQS, an in-memory bus) simply does not implement it.

type DatabasePool

type DatabasePool interface {
	// Client supplies the Start/Stop lifecycle and Kubernetes health probes.
	Client

	// DB returns the underlying *sql.DB the ORM adapters and raw-SQL callers share.
	// It is nil until Start opens the pool.
	DB() *sql.DB
}

DatabasePool is a lifecycle-managed database connection pool: it is started and stopped by a lifecycle.Manager, health-probed by Kubernetes (Liveness/Readiness), and exposes the shared *sql.DB the ORM adapters and raw-SQL callers run over. The backend (PostgreSQL today) is selected by the database tier's Kind, so switching database engines is a config change, not a caller edit.

type DeadLetter

type DeadLetter struct {
	// ID identifies the dead-lettered message (typically the source message id).
	ID string
	// Reason records why processing failed and the message was dead-lettered.
	Reason string
	// Payload is the original message body, preserved for inspection or redrive.
	Payload []byte
}

DeadLetter is a message that could not be processed and is routed to a dead-letter sink for later inspection or redrive.

type DeadLetterBackend

type DeadLetterBackend interface {
	// Send durably records the dead letter, returning an error if the sink is
	// unavailable so the caller can decide delete-vs-redrive of the source message.
	Send(ctx context.Context, letter DeadLetter) error
}

DeadLetterBackend durably records dead letters (e.g. an SQS dead-letter queue).

Implementations: SQS (production), an in-memory stub (tests/local). All handler code depends on this interface, never on a concrete backend — swap via a deadletter factory.

type DecoratedRepository

type DecoratedRepository[T any, P any, ID comparable] interface {
	// Repository is the base CRUD surface the decorator stack wraps.
	Repository[T, P, ID]
}

DecoratedRepository is the fully-decorated CRUD surface of a repository (the base Repository methods wrapped by the resilience + observability decorator stack). It is the type the decorator builder returns and that concrete repositories embed. Custom (non-CRUD) methods no longer decorate through a method here — they run through the type-agnostic decorate.OpMiddleware chain via decorate.Exec[R], which lets them return any type without the former (*T,error) shoehorn.

type DecoratedService

type DecoratedService[T any, P any, ID comparable] interface {
	// Service is the base CRUD surface the decorator stack wraps.
	Service[T, P, ID]
}

DecoratedService is the fully-decorated surface of a service (the base Service methods wrapped by the decorator stack). It is the type the service decorator builder returns and that concrete services embed. Custom (non-CRUD) methods no longer decorate through a method here — they run through the type-agnostic decorate.OpMiddleware chain via decorate.Exec[R], which lets them return any type without the former (*T,error) shoehorn.

type Deduplicator

type Deduplicator interface {
	// Seen atomically reports whether key was already processed and, if not, marks it
	// processed. It returns true when key is a duplicate (the caller should skip it) and
	// false when this is the first sighting (the caller should process it).
	Seen(ctx context.Context, key string) (bool, error)
}

Deduplicator provides idempotency for at-least-once delivery: it records the keys it has seen so a redelivered message is processed exactly once.

Implementations: an in-memory TTL store (default), or a shared store (Redis/Postgres) for cross-replica dedup. Handler code depends on this interface, never a concrete store.

type Deleter

type Deleter[ID comparable] interface {
	// Delete removes an entity by ID.
	Delete(ctx context.Context, id ID) error
}

Deleter removes an entity by its identifier.

type Discoverer

type Discoverer[T any] interface {
	// Discover returns the work units found under root.
	Discover(ctx context.Context, root string) ([]T, error)
}

Discoverer discovers the set of work units for a job to iterate over.

type DiscovererFunc

type DiscovererFunc[T any] func(ctx context.Context, root string) ([]T, error)

DiscovererFunc is a function that implements Discoverer[T].

func (DiscovererFunc[T]) Discover

func (f DiscovererFunc[T]) Discover(ctx context.Context, root string) ([]T, error)

Discover implements Discoverer[T] by calling the function.

type DistributedLock

type DistributedLock interface {
	// Acquire attempts to take key without blocking. On success it returns an
	// opaque fencing token and acquired=true; if the key is already held it
	// returns an empty token and acquired=false. The token must be presented to
	// Renew and Release so a previous holder cannot affect the current one.
	Acquire(ctx context.Context, key string) (token string, acquired bool, err error)

	// Renew extends the lease on key iff token still holds it. held=true means
	// the caller still owns the lock; held=false means the lease was lost (it
	// expired, was released, or a different holder now owns the key) and the
	// caller must stop treating itself as the holder. err is reserved for
	// backend/transport failures, distinct from a cleanly lost lease.
	Renew(ctx context.Context, key, token string) (held bool, err error)

	// Release frees key iff token still holds it (token-fenced). Releasing a key
	// held by a different token, or an unheld key, is a no-op — never an error —
	// so a late Release from a previous holder cannot free a new holder's lock.
	Release(ctx context.Context, key, token string) error
}

DistributedLock is a keyed, token-fenced, best-effort mutual-exclusion primitive: it lets one holder claim a string key across processes, so that "at most one active operation per key" holds across service replicas (high-cardinality, short-held, best-effort; a Postgres advisory lock suits a long-running single writer instead).

It is the seam a single-active-operation guard depends on; callers depend on this interface, never a concrete backend. Backends: clients/lock/local (in-process, dev/single-replica) and clients/lock/redis (cross-pod, staging/prod), selected by config via clients/lock.NewFromConfig.

Best-effort, NOT true mutual exclusion: a rare double-acquire is possible if a holder stalls past the lease TTL, or on a Redis primary→replica failover that loses an unacknowledged write (the Redlock critique). Callers must tolerate a rare duplicate — the Locker.Hold helper mitigates the stall case by cancelling the in-flight work the instant a renewal reports the lease lost.

type DynamicConsumer

type DynamicConsumer interface {
	// MessageConsumer provides the base Subscribe/Close operations.
	MessageConsumer

	// Unsubscribe stops delivering messages from topic. It is a no-op if topic
	// was never subscribed; a broker error is returned.
	Unsubscribe(ctx context.Context, topic string) error
}

DynamicConsumer is a MessageConsumer that can also remove a per-channel subscription at runtime. Pub/Sub brokers (e.g. Redis) multiplex many channels over one subscription and add/remove them dynamically; SQS-style queue consumers do not. It is an opt-in capability interface that composes with MessageConsumer (ARCHITECTURE.md#interface-composition) rather than widening the shared contract — backends that cannot unsubscribe simply do not implement it.

type Encryptor

type Encryptor interface {
	// Encrypt seals plaintext, returning a nonce-prefixed ciphertext.
	Encrypt(plaintext []byte) ([]byte, error)
	// Decrypt opens a ciphertext produced by Encrypt, returning the original plaintext.
	// It errors on a wrong key or a tampered ciphertext.
	Decrypt(ciphertext []byte) ([]byte, error)
}

Encryptor seals and opens byte payloads with authenticated encryption. Encrypt returns an opaque, nonce-prefixed ciphertext; Decrypt reverses it and fails on a wrong key or a tampered ciphertext (the authentication tag). It is the shared data-at-rest sealing contract — e.g. connector credentials sealed before they are written to Postgres. The concrete backend (AES-256-GCM, in foundation/crypto) is injected from configuration, never referenced directly.

type EventPublisher

type EventPublisher interface {
	// Publish publishes a single domain event.
	Publish(ctx context.Context, event events.Event) error

	// PublishBatch publishes multiple domain events.
	PublishBatch(ctx context.Context, events []events.Event) error
}

EventPublisher publishes domain events, typically via a transactional outbox.

type ExecutionObserver

type ExecutionObserver interface {
	// OnGateStart is called once when the gate begins, before any phase runs,
	// with the names of the phases it will execute.
	OnGateStart(name string, phases []string)

	// OnPhaseStart is called once when a phase begins, before any job runs,
	// with the names of the jobs it will execute.
	OnPhaseStart(name string, jobs []string)

	// OnStepStart is called when an individual step begins, before it runs.
	// Best-effort: producers that don't know a step's name up front may skip it.
	OnStepStart(name, group string)

	// OnStepComplete is called after each individual step finishes.
	OnStepComplete(result types.StepResult)

	// OnPhaseComplete is called once after all jobs in a gate phase finish,
	// with the phase's aggregate results and measured wall-clock elapsed.
	OnPhaseComplete(name string, results types.StepResults, elapsed time.Duration)

	// OnGateComplete is called when the full gate (all phases) finishes, with the
	// gate's aggregate results and measured total wall-clock elapsed.
	OnGateComplete(gateName string, results types.StepResults, elapsed time.Duration)
}

ExecutionObserver receives lifecycle events from the execution engine. All methods are called synchronously, serialized on a single goroutine — the engine owns serialization (RunJobGroup/FanOut funnel concurrent results through one goroutine before delivering them here), so implementations need not lock for that reason; they still must not block.

Lifecycle order per run: OnGateStart → (per phase: OnPhaseStart → per step: OnStepStart? → OnStepComplete) → OnPhaseComplete → … → OnGateComplete. The *Start events carry only the names known at that point (phase names at the gate, job names at the phase); OnStepStart is best-effort and may be omitted by producers that don't know a step's name before it runs.

type Exister

type Exister[ID comparable] interface {
	// Exists checks if an entity with the given ID exists.
	Exists(ctx context.Context, id ID) (bool, error)
}

Exister reports whether an entity with the given identifier exists.

type FS

type FS interface {
	// Stat returns a FileInfo describing the named file.
	Stat(name string) (fs.FileInfo, error)

	// MkdirAll creates a directory named path, along with any necessary parents.
	MkdirAll(path string, perm fs.FileMode) error

	// ReadFile reads the named file and returns the contents.
	ReadFile(name string) ([]byte, error)

	// WriteFile writes data to the named file, creating it if necessary.
	WriteFile(name string, data []byte, perm fs.FileMode) error

	// RemoveAll removes path and any children it contains.
	RemoveAll(path string) error

	// Rename renames (moves) oldpath to newpath.
	Rename(oldpath, newpath string) error

	// ReadDir reads the named directory and returns all its directory entries
	// sorted by filename.
	ReadDir(name string) ([]fs.DirEntry, error)
}

FS is a filesystem abstraction interface that wraps common os package operations. Implementations must be safe for concurrent use.

type Gauge

type Gauge interface {
	// Set sets the gauge to the given value.
	Set(value float64, labelValues ...string)

	// Inc increments the gauge by 1.
	Inc(labelValues ...string)

	// Dec decrements the gauge by 1.
	Dec(labelValues ...string)
}

Gauge records point-in-time values.

type GitOps

type GitOps interface {
	// HasPathsChanged reports whether any of the given paths differ between
	// two git refs. Returns true when differences exist.
	HasPathsChanged(
		ctx context.Context,
		root, from, to string,
		paths []string,
	) (bool, error)

	// StageFiles runs git add on the given paths relative to root.
	StageFiles(ctx context.Context, root string, paths []string) error

	// StagedFiles returns root-relative paths of staged files matching the
	// given glob patterns (e.g., "*.go", "*.py").
	StagedFiles(ctx context.Context, root string, patterns []string) ([]string, error)
}

GitOps abstracts git index and diff operations needed by conditional and staged-file job wrappers. Implementations live in the CLI layer (e.g., foundation/git.Ops) so consumer packages stay free of git dependencies.

type HealthChecker

type HealthChecker interface {
	// Liveness reports whether the component is alive (a lightweight self-check that
	// does not depend on external systems).
	Liveness(ctx context.Context) error

	// Readiness reports whether the component can serve traffic — typically by
	// verifying connectivity (e.g. a Ping).
	Readiness(ctx context.Context) error
}

HealthChecker reports a component's health for Kubernetes probes: Liveness answers "is it alive?" (restart the pod if not) and Readiness "is it ready to serve?" (remove the pod from the load balancer if not).

type Hedger

type Hedger interface {
	// Hedge runs op, racing a second concurrent attempt after the configured delay, and
	// returns the result of the first attempt to complete. The slower attempt is cancelled
	// via ctx. Returns the first responder's error, or ctx's error if it is cancelled first.
	Hedge(ctx context.Context, op func() error) error
}

Hedger reduces tail latency by racing a backup attempt: it starts op, and if op has not returned within the hedge delay, starts a second concurrent op and takes whichever responds first (cancelling the loser). Only safe for IDEMPOTENT / read-only operations — a second attempt must not double a side effect — so callers opt in per operation.

A peer resilience contract of Retrier and Bulkhead: like them it wraps an operation with a policy and depends on no concrete implementation. Swap implementations via the hedge.New factory.

type Histogram

type Histogram interface {
	// Observe records a value with the given label values.
	Observe(value float64, labelValues ...string)
}

Histogram records value distributions.

type Inserter

type Inserter[T any] interface {
	// Insert persists a new entity; append-only, so it returns only an error.
	Insert(ctx context.Context, entity *T) error
}

Inserter is the append-only write role: a single Insert that persists a new entity and reports only success/failure. Unlike Writer (Create returns the stored entity + Update mutates), an append-only store — an outbox relay, an audit log — only ever adds immutable rows, so it embeds just this. It is NOT part of the Store/Repository/Service composition (those use Writer.Create); it is the standalone role an append-only resource embeds in place of Writer.

type Job

type Job interface {
	// Kind returns the job type identifier used for worker dispatch.
	Kind() string

	// Args returns the job's payload as a key-value map.
	Args() map[string]interface{}
}

Job represents a background job that can be enqueued for processing.

type JobEnqueuer

type JobEnqueuer interface {
	// Enqueue submits a job for asynchronous processing.
	Enqueue(ctx context.Context, job Job) error

	// Close releases resources held by the enqueuer.
	Close() error
}

JobEnqueuer enqueues background jobs for asynchronous processing.

type JobFactory

type JobFactory interface {
	// Job builds and returns the configured AnyJob.
	Job() AnyJob
}

JobFactory is a typed producer of an AnyJob. CLI pipeline types implement it so a job is a modeled value (with its parameters as fields) rather than a bare factory function. Callers invoke Job() to obtain the runnable AnyJob.

type JobScheduler

type JobScheduler interface {
	// Schedule adds a periodic job to the schedule at the given interval.
	Schedule(job PeriodicJob, interval time.Duration) error
}

JobScheduler schedules periodic background jobs.

type LeaderElector

type LeaderElector interface {
	// IsLeader reports whether this instance currently holds leadership.
	IsLeader(ctx context.Context) (bool, error)
}

LeaderElector coordinates single-instance execution: only the elected leader runs the guarded work, so a job scheduled across N replicas fires once.

Implementations: a single-process elector that is always the leader (dev/single-replica), or a distributed elector (a Postgres advisory lock, the sibling of clients/lock's postgres backend). Job code depends on this interface, never a concrete elector.

type Lifecycle

type Lifecycle interface {
	// Start initializes the component and establishes its connection/resources. It
	// should block until the component is ready or ctx is cancelled.
	Start(ctx context.Context) error

	// Stop gracefully releases the component's resources and completes in-flight
	// work.
	Stop(ctx context.Context) error
}

Lifecycle is implemented by any client/component with coordinated startup and shutdown, so a lifecycle.Manager can Start them in registration order and Stop them in reverse for graceful shutdown.

type ListRunner

type ListRunner[T any] interface {
	// List applies the resolved spec (predicates + order + window) to the scoped base query and
	// returns the matching rows in order.
	List(ctx context.Context, spec types.ListSpec) ([]T, error)

	// Count returns the number of rows matching the given resolved predicates over the scoped base
	// query (no window), for the offset pager's total_count — BOUNDED at `bound` (≥ 1): it counts at
	// most `bound` rows (a LIMIT-bounded id fetch, O(bound)), so a per-page total_count never pays a
	// full-table COUNT on a huge set. The caller (foundation/listquery.Run) passes
	// count_cap+1 and turns an over-cap tally into an estimate; the runner just reports the bounded
	// tally.
	Count(ctx context.Context, filters []types.Filter, bound int) (int, error)
}

ListRunner executes a resolved list query against one datastore entity. It is the EXECUTION seam of the list-query platform — the dual of QueryBackend (the query-LANGUAGE seam): QueryBackend folds the IR into predicates, ListRunner runs a composed query and returns typed rows.

It is the single per-entity adapter the generic pagination template (foundation/listquery.Run) drives, so filter, sort, and keyset pagination are written ONCE in Run and every store's List is just "build the scoped base, hand it to Run." A runner is constructed per request over the request-scoped base query (tenant and access scoping already applied); it applies the spec's resolved predicates + order + window on top and executes. Ent has no generic query interface, so the adapter is per-entity — but it is dumb and uniform (a codegen candidate).

T is the entity row type (e.g. *ent.Document).

type Lister

type Lister[T any, P any] interface {
	// List retrieves entities matching the given parameters with pagination.
	List(ctx context.Context, params P, page types.PageRequest) (*types.Page[T], error)
}

Lister retrieves a paginated set of entities matching filter parameters.

type Locker

type Locker interface {
	// DistributedLock provides the primitive Acquire/Renew/Release operations.
	DistributedLock

	// Hold acquires key and, on success, keeps its lease alive with a background
	// watchdog that renews on the configured interval. It returns:
	//   - held: a child of ctx that is cancelled the instant the lease is lost (a
	//     Renew reports held=false or errors) or when release is called; run the
	//     guarded work under held so it stops the moment the lock is no longer ours.
	//   - release: stops the watchdog, cancels held, and frees the lock
	//     (token-fenced); idempotent, safe to defer, and frees the key even if ctx
	//     is already cancelled.
	//   - acquired: false if the key was already held by someone else (held is then
	//     a cancelled context and release a no-op — the caller should reject the
	//     request); err is returned only for a backend failure on Acquire.
	Hold(
		ctx context.Context,
		key string,
	) (held context.Context, release func(), acquired bool, err error)
}

Locker is the consumer-facing lock seam: a DistributedLock plus Hold, the managed acquire-with-renewal-watchdog helper. clients/lock.NewFromConfig returns a Locker, and a single-active-operation guard depends on this interface.

Hold is declared here rather than on DistributedLock so the primitive backend contract stays minimal (interface-segregation): each backend implements only Acquire/Renew/Release, while Hold's backend-agnostic orchestration (the renewal watchdog and lease-lost cancellation) is provided once by the wrapper that composes it over any DistributedLock.

type Logger

type Logger interface {
	// Debug logs at DEBUG level with optional key-value pairs.
	Debug(msg string, keysAndValues ...any)

	// Info logs at INFO level with optional key-value pairs.
	Info(msg string, keysAndValues ...any)

	// Warn logs at WARN level with optional key-value pairs.
	Warn(msg string, keysAndValues ...any)

	// Error logs at ERROR level with optional key-value pairs.
	Error(msg string, keysAndValues ...any)

	// With returns a new Logger with the given key-value pairs pre-bound.
	With(keysAndValues ...any) Logger

	// WithContext returns a new Logger that extracts values (e.g. correlation ID)
	// from the context on each log call.
	WithContext(ctx context.Context) Logger
}

Logger provides structured, leveled logging.

Implementations: zap-backed slog (default), stdlib slog, or any structured logger that supports key-value pairs.

All foundation and service code depends on this interface, never on a concrete logger. Swap implementations via the logger.New factory.

type Message

type Message struct {
	// Metadata holds optional key-value pairs attached to the message.
	Metadata map[string]string `json:"metadata,omitempty"`

	// ID is the unique identifier of the message.
	ID string `json:"id"`

	// Topic is the queue or topic the message was received from.
	Topic string `json:"topic"`

	// Payload is the raw message body.
	Payload []byte `json:"payload"`

	// Timestamp is the Unix timestamp when the message was produced.
	Timestamp int64 `json:"timestamp"`
}

Message represents a message received from a queue.

type MessageConsumer

type MessageConsumer interface {
	// Subscribe starts consuming messages from the specified topic.
	// The handler is called for each message. Return nil to ack, error to nack.
	Subscribe(ctx context.Context, topic string, handler MessageHandler) error

	// Close stops the consumer gracefully.
	Close() error
}

MessageConsumer consumes messages from a queue/topic, acking or nacking each one by its handler's result.

type MessageHandler

type MessageHandler func(ctx context.Context, msg *Message) error

MessageHandler processes a single message.

type MessagePublisher

type MessagePublisher interface {
	// Publish sends a message to the specified topic.
	Publish(ctx context.Context, topic string, payload []byte) error

	// PublishBatch sends multiple messages to the specified topic.
	PublishBatch(ctx context.Context, topic string, payloads [][]byte) error
}

MessagePublisher publishes messages (typically domain events) to a message broker (SQS, Watermill, etc.).

type Metrics

type Metrics interface {
	// Counter returns a counter that can be incremented.
	Counter(name, help string, labels ...string) Counter

	// Histogram returns a histogram for recording distributions.
	Histogram(name, help string, buckets []float64, labels ...string) Histogram

	// Gauge returns a gauge for recording point-in-time values.
	Gauge(name, help string, labels ...string) Gauge

	// Handler returns an HTTP handler that serves the metrics endpoint.
	Handler() http.Handler
}

Metrics provides application-level metric recording.

Implementations: Prometheus (default) or any metrics backend that supports counters, histograms, and gauges.

All foundation and service code depends on this interface, never on a concrete metrics library. Swap implementations via the metrics.New factory.

type MultipartUpload

type MultipartUpload struct {
	// Key is the object key the upload will assemble into.
	Key string `json:"key"`

	// UploadID is the S3 multipart upload id (needed to abort or complete it).
	UploadID string `json:"upload_id"`

	// Initiated is the RFC3339 timestamp the upload was created, or "" if unknown.
	Initiated string `json:"initiated"`
}

MultipartUpload describes an in-progress multipart upload reported by ListMultipartUploads — the seam the cleanup sweep uses to find and abort abandoned uploads (which are invisible to ListObjects until completed).

type Named

type Named interface {
	// Meta returns the unit's identity for display and tool resolution.
	Meta() types.JobMeta
}

Named is the identity half of an execution unit: its Meta for display and tool resolution. Both command jobs (AnyJob) and runner-less units (Runnable) compose it, so the gate can label either without knowing how it executes.

type OpService

type OpService[A any, R any] interface {
	// Name returns the service name, used as a label in logs, metrics, and
	// decorator actions.
	Name() string

	// Run executes the operation described by args and returns its result. It is
	// the single decoration chokepoint: cross-cutting concerns (logging, metrics,
	// tracing, recovery) wrap Run rather than each individual operation.
	Run(ctx context.Context, args A) (R, error)
}

OpService is the generic operation-service contract implemented by CLI services. Unlike Service[T,P,ID] (entity CRUD), an OpService exposes a single decoratable entrypoint: a caller runs one of the service's operations by passing typed Args and receiving a typed Result. Multi-operation services model their operations as a tagged union in Args (an operation discriminator plus per-operation fields) and switch on it inside Run.

A is the operation argument type (often a tagged union for multi-op services); R is the operation result type.

Result-type contract. An OpService's R is one of exactly two archetypes, so operation services stay uniform:

  • types.StepResults — GATE-PRODUCING: the operation fans out into per-item steps that a gate renders (lint, test suites, build, format, security, …). The error channel signals a hard failure; the steps carry per-item pass/fail/warn.
  • struct{} — CMD-DIRECT: the operation has no per-item breakdown; success/failure is carried entirely in Run's error (codegen, migrate, check, …).

Any other R is drift to be migrated to one of these two.

type PatternConsumer

type PatternConsumer interface {
	// DynamicConsumer provides the base consume operations plus per-channel Unsubscribe.
	DynamicConsumer

	// PSubscribe starts delivering messages from every channel matching pattern
	// (glob syntax) to handler. Runtime add.
	PSubscribe(ctx context.Context, pattern string, handler MessageHandler) error

	// PUnsubscribe stops delivering messages for pattern. It is a no-op if pattern
	// was never subscribed; a broker error is returned. Runtime remove.
	PUnsubscribe(ctx context.Context, pattern string) error
}

PatternConsumer is a DynamicConsumer that also supports glob-pattern (wildcard) subscriptions — the Redis PSUBSCRIBE model, where one subscription matches every channel whose name matches a pattern. It composes with DynamicConsumer (and thus MessageConsumer) per ARCHITECTURE.md#interface-composition; backends without pattern matching do not implement it.

type PeriodicJob

type PeriodicJob struct {
	// Args holds arbitrary key-value arguments passed to the job worker.
	Args map[string]interface{}

	// Kind is the job type identifier used to match a registered worker.
	Kind string

	// Schedule is a cron expression defining when the job runs.
	Schedule string
}

PeriodicJob represents a job that runs on a schedule.

type Pipeline

type Pipeline[In any, Out any] interface {
	// Execute transforms the input into output.
	// Context is provided for cancellation and tracing.
	// Implementations should be stateless and safe for concurrent use.
	Execute(ctx context.Context, input In) (Out, error)
}

Pipeline is the generic data transformation interface for stateless sequential operations. In = input type, Out = output type.

Pipelines model stateless sequential transformations where each stage consumes the output of the prior stage. Unlike workflows, pipelines do not coordinate state or orchestrate multiple services — they focus on pure data transformation.

Examples: NLP processing (tokenization → POS tagging → NER), document parsing (text extraction → metadata extraction → chunking), ETL transformations.

type QueryBackend

type QueryBackend[Q, S any] interface {
	// Clause interprets a single leaf comparison — storage column <op> value — into a predicate.
	// col is already resolved from the allow-list; v is the parser-validated operand (a scalar,
	// or a []any for OpIn/OpNotIn).
	Clause(col string, op types.FilterOperator, v any) Q

	// ExistsClause interprets a leaf comparison whose column lives on a JOINED table into a
	// correlated EXISTS predicate: the base row matches when a row in join.Table (correlated by
	// join.TargetKey = <base>.join.LocalKey) satisfies op/v on one of join.Columns (ORed when >1). It
	// never joins the base query, so scope/soft-delete/keyset are untouched. v is the parser-validated
	// operand (a scalar, or a []any for OpIn/OpNotIn), same as Clause.
	ExistsClause(join types.JoinTarget, op types.FilterOperator, v any) Q

	// And combines sub-predicates so that every one must match.
	And(subs []Q) Q

	// Or combines sub-predicates so that at least one must match.
	Or(subs []Q) Q

	// Empty is the no-op (match-all) predicate for an absent or empty filter.
	Empty() Q

	// Order interprets one sort directive — storage column, descending when desc — into a sort node.
	Order(col string, desc bool) S

	// OrderJoin interprets a sort directive whose column lives on a JOINED table into a sort
	// node that orders by a correlated subquery over join.Table (correlated by join.TargetKey =
	// <base>.join.LocalKey), one ORDER BY term per join.Columns entry (a member "name" over
	// first_name+last_name). Like ExistsClause it never joins the base query. Joined sorts always ride
	// the offset pager (keyset stays same-table), so no keyset-seek counterpart is needed.
	OrderJoin(join types.JoinTarget, desc bool) S

	// OrderOrdinal interprets a sort directive with a value ORDINAL into a sort node that
	// orders col by the given values' positions (`CASE col WHEN v0 THEN 0 … ELSE len END`), so an enum
	// column sorts by meaning (owner<editor<viewer) rather than alphabetically. values are the column's
	// DB values in ascending order. An ordinal sort is a custom sort (the offset pager); no keyset seek.
	OrderOrdinal(col string, values []string, desc bool) S

	// OrderDerived interprets a COMPUTED / CORRELATED sort directive into a sort node: a
	// correlated aggregate over a child table (d.Aggregate — a Documents/Members COUNT) or a
	// timestamp-plus-interval (d.Interval — a connector's Next Sync = last_sync_at + the schedule's
	// interval). Exactly one arm of d is set. Like OrderJoin it renders an ORDER BY over an expression /
	// correlated subquery and never joins the base query; a derived sort is a custom sort (the offset
	// pager), so there is no keyset-seek counterpart.
	OrderDerived(d types.DerivedSort, desc bool) S
}

QueryBackend interprets the list-query IR (types.Filter / types.OrderField) into a concrete datastore query. It is the "algebra" of the multi-target query-compilation SDK: the shared fold in foundation/listquery walks the IR and calls these methods, and each datastore provides exactly one implementation (Ent now; Mongo/Elasticsearch/GORM future).

Q is the backend's predicate node type (e.g. Ent's func(*sql.Selector), Mongo's bson.M); S is its sort node type. Q and S are separate type parameters because a filter and a sort are not the same structure in every datastore: Ent unifies both as func(*sql.Selector), but Mongo is a bson.M filter vs a bson.D sort, and Elasticsearch a query fragment vs a sort array. Keeping them distinct means the first non-SQL backend can model its sort natively without a breaking change to this core interface.

type RateLimiter

type RateLimiter interface {
	// Allow reports whether an event may happen now (non-blocking).
	Allow() bool

	// Wait blocks until the limiter permits an event or ctx is cancelled.
	Wait(ctx context.Context) error
}

RateLimiter controls the rate of operations using a token bucket algorithm.

Implementations: token bucket via golang.org/x/time/rate (default).

All service code depends on this interface, never on a concrete rate limiter. Swap implementations via the ratelimiter.New factory.

type Reader

type Reader[T any, ID comparable] interface {
	// Get retrieves an entity by ID.
	Get(ctx context.Context, id ID) (*T, error)
}

Reader retrieves a single entity by its identifier.

type ReplayBuffer

type ReplayBuffer interface {
	// Append records payload (identified by msgID) as the newest entry under key, evicting the
	// oldest beyond the configured bound and refreshing the key's TTL.
	Append(ctx context.Context, key, msgID string, payload []byte) error

	// ReplayAfter returns the retained messages strictly after afterMsgID, in insertion order.
	// complete is true only when afterMsgID is a proven-gapless anchor — either present in the
	// retained window (a gapless tail follows, possibly empty when the caller is already caught up)
	// or exactly the low-water mark set by the last Prune. complete is false for anything else —
	// an evicted or unknown id, a TTL-expired or absent key, or an empty afterMsgID (a client that
	// received nothing; unreachable in practice) — so the caller must full-refresh, never replay a
	// possibly-gapped tail. complete=false is always the safe answer.
	ReplayAfter(
		ctx context.Context,
		key, afterMsgID string,
	) (msgs []BufferedMessage, complete bool, err error)

	// Prune drops every retained entry up to and including upToMsgID — a confirmed prefix the client
	// has acked — leaving the tail, and records upToMsgID as the new low-water mark. An upToMsgID not
	// in the retained window is a no-op.
	Prune(ctx context.Context, key, upToMsgID string) error

	// Delete removes the key's buffer entirely (e.g. after a successful replay).
	Delete(ctx context.Context, key string) error
}

ReplayBuffer buffers a keyed, ordered tail of recent outbound messages so a client whose socket briefly drops can be resent exactly the frames it missed (reconnect replay). It is the seam the WebSocket hub depends on; callers depend on this interface, never a concrete backend. Backends: clients/replaybuffer/memory (in-process, dev/single-replica — a reconnect lands on the same pod) and clients/replaybuffer/redis (cross-pod, staging/prod), selected by config via clients/replaybuffer.NewFromConfig (mirroring clients/lock).

Ordering is INSERTION order, never a comparison of the opaque ids ("msg-10" sorts before "msg-9" lexically) — ReplayAfter/Prune locate an id by its position in the retained list, so callers must never assume the ids are numerically comparable.

type Repository

type Repository[T any, P any, ID comparable] interface {
	// Reader provides Get (retrieve by id).
	Reader[T, ID]
	// Lister provides List (paginated query by params).
	Lister[T, P]
	// Writer provides Create and Update.
	Writer[T, ID]
	// Deleter provides Delete (remove by id).
	Deleter[ID]
	// Exister provides Exists (existence check by id).
	Exister[ID]
}

Repository is the generic data access interface, composed from the role interfaces — the same full-CRUD surface as before, now assembled from Reader/Lister/Writer/Deleter/Exister so a resource can depend on only the roles it uses. T = entity type, P = query/filter params, ID = identifier type.

type Retrier

type Retrier interface {
	// Retry executes op with automatic retry on transient failures.
	// Returns nil on success, or the last error after all retries are exhausted.
	Retry(ctx context.Context, op func() error) error
}

Retrier executes operations with automatic retry on transient failures.

Implementations: exponential backoff with jitter (default).

All service code depends on this interface, never on a concrete retry library. Swap implementations via the retry.New factory.

type Runnable

type Runnable interface {
	// Named supplies the unit's identity (Meta) for display and tool resolution.
	Named

	// Run performs the unit's in-process work and returns collected step results.
	Run(ctx context.Context) (types.StepResults, error)
}

Runnable is a runner-less execution unit: in-process work (e.g. DB seeding) that needs neither a CommandRunner nor a filesystem root. Compose it into a gate with the execution adapter (job.Adapt), which lifts it to AnyJob — so callers build typed Runnables instead of passing a nil CommandRunner through the API.

type ServerTransport

type ServerTransport interface {
	// Client supplies the Start/Stop lifecycle and Kubernetes health probes.
	Client

	// RegisterFunc mounts an http.HandlerFunc at a path pattern.
	RegisterFunc(pattern string, handler http.HandlerFunc)

	// Mux returns the underlying request multiplexer for direct handler registration
	// (e.g. mounting a generated Connect handler at its path prefix).
	Mux() *http.ServeMux
}

ServerTransport is a lifecycle-managed, health-checkable HTTP/RPC server: it is started and stopped by a lifecycle.Manager, health-probed by Kubernetes (Liveness/Readiness), and exposes handler registration plus its request mux. The backend (Connect over net/http today) is selected by the transport tier's Kind, so switching server frameworks is a config change, not a caller edit.

type Service

type Service[T any, P any, ID comparable] interface {
	// Reader provides Get (retrieve by id).
	Reader[T, ID]
	// Lister provides List (paginated query by params).
	Lister[T, P]
	// Writer provides Create and Update.
	Writer[T, ID]
	// Deleter provides Delete (remove by id).
	Deleter[ID]
}

Service is the generic business logic layer interface, composed from the role interfaces. It carries the same Get/List/Create/Update/Delete surface as before (no Exists — existence checks are a store/repository concern), now assembled from Reader/Lister/Writer/Deleter so a service can depend on only the roles it uses. T = entity type, P = query/filter params, ID = identifier type.

Services wrap repositories with business rules, authorization, validation, event publishing, and caching.

type ServiceHooks

type ServiceHooks[T any] interface {
	// BeforeCreate is called before entity creation. Can modify the entity or return an error.
	BeforeCreate(ctx context.Context, entity *T) error

	// AfterCreate is called after successful entity creation.
	AfterCreate(ctx context.Context, entity *T) error

	// BeforeUpdate is called before entity update. Can modify the entity or return an error.
	BeforeUpdate(ctx context.Context, entity *T) error

	// AfterUpdate is called after successful entity update.
	AfterUpdate(ctx context.Context, entity *T) error

	// BeforeDelete is called before entity deletion.
	BeforeDelete(ctx context.Context, id any) error

	// AfterDelete is called after successful entity deletion.
	AfterDelete(ctx context.Context, id any) error
}

ServiceHooks provides lifecycle hooks for service operations. Implementations can override specific hooks while using no-op defaults for others.

type ServiceTokenValidator

type ServiceTokenValidator interface {
	// Validate returns the calling service's name for a valid token, or ok=false when
	// the token matches no known caller.
	Validate(token string) (caller string, ok bool)
}

ServiceTokenValidator validates a service-to-service bearer token and returns the identity of the calling service. It is the swappable Strategy behind the internal service-auth interceptor: the engine ships a static per-caller token validator, and a workload-identity validator (K8s SA projected tokens / mesh mTLS) can replace it without touching the interceptor or its call sites.

type Source added in v0.1.2

type Source interface {
	// Get resolves the credential referenced by ref, returned as an opaque Secret. A
	// missing credential is a coded not-found error, not an empty Secret.
	Get(ctx context.Context, ref types.Ref) (types.Secret, error)

	// Put stores value for ref. Read-only backends (environment variables, and cloud
	// secret managers written out-of-band by IaC) return a coded unsupported-operation
	// error rather than silently succeeding.
	Put(ctx context.Context, ref types.Ref, value types.Secret) error
}

Source resolves credentials from a configured backend — an environment variable, a git-ignored local file, or a cloud secret manager — selected by Kind at construction. It is the single seam through which any consumer reads or writes a credential, so switching a backend is a configuration change, not a caller edit.

type SourceBuilder

type SourceBuilder interface {
	// Build constructs the ConnectorSource for cfg.Kind, or returns a coded error on an unknown kind
	// or a construction failure.
	Build(ctx context.Context, cfg SourceConfig) (ConnectorSource, error)
}

SourceBuilder builds a ConnectorSource from a SourceConfig, failing loudly on an unknown kind (the foundation/logger NewFromConfig pattern). It is the seam the connector service depends on and the composition root injects; the concrete builder lives in go/clients/connector.

type SourceConfig

type SourceConfig struct {
	// Kind selects the connector backend.
	Kind ConnectorKind
	// Bucket names the source object-store bucket (S3 kind).
	Bucket string
	// Prefix restricts the listing to objects under this key prefix (S3 kind).
	Prefix string
	// Region is the connector's bucket region (S3 kind); empty falls back to the client default. The
	// object-store ENDPOINT is deliberately NOT here — it is app-infra (dev MinIO), captured by the
	// composition root's client builder, not a per-connector value.
	Region string
	// AuthMethod selects the credential strategy: "iam_role" (STS AssumeRole) or "access_key" (static).
	AuthMethod string
	// IAMRoleARN is the role assumed for auth_method=iam_role.
	IAMRoleARN string
	// ExternalID is the STS ExternalId sent on every AssumeRole for auth_method=iam_role
	// (required there). The role's trust policy requires it, which ties the role to the
	// tenant that owns it: without it, a tenant who registers another tenant's role ARN
	// could have the platform assume it for them (the confused-deputy problem). The
	// consumer's platform generates it per tenant and stores it with the connector; it is
	// never taken from tenant input, or a tenant could present another tenant's role ARN
	// together with that tenant's id. Format (STS's rule): 2–1224 characters of
	// [\w+=,.@:/-]; the S3 builder rejects anything else with CodeInvalidInput.
	ExternalID string
	// AccessKeyID is the access key id for auth_method=access_key.
	AccessKeyID string
	// SecretAccessKey is the secret for auth_method=access_key (already-decrypted plaintext; never log it).
	SecretAccessKey string
}

SourceConfig is the primitive, domain-free, AWS-free description a SourceBuilder turns into a ConnectorSource. The consumer maps its own connector record + decrypted credentials onto this at its composition root, so this package never imports consumer types. Its credential fields are already-decrypted plaintext (the consumer owns encryption-at-rest); never log them.

type Span

type Span interface {
	// End completes the span.
	End()

	// SetAttribute adds a key-value attribute to the span.
	SetAttribute(key string, value any)

	// RecordError records an error on the span.
	RecordError(err error)

	// SetStatus sets the span status.
	SetStatus(code SpanStatusCode, description string)
}

Span represents a single operation within a trace.

type SpanConfig

type SpanConfig struct {
	// Kind specifies the span kind (client, server, producer, consumer, internal).
	Kind SpanKind
}

SpanConfig holds span creation configuration.

type SpanKind

type SpanKind int

SpanKind identifies the role of a span in a trace.

const (
	// SpanKindInternal is the default span kind, for work internal to a service.
	SpanKindInternal SpanKind = iota
	// SpanKindServer marks a span handling an inbound request.
	SpanKindServer
	// SpanKindClient marks a span issuing an outbound request.
	SpanKindClient
	// SpanKindProducer marks a span publishing a message to a broker.
	SpanKindProducer
	// SpanKindConsumer marks a span consuming a message from a broker.
	SpanKindConsumer
)

type SpanOption

type SpanOption func(*SpanConfig)

SpanOption configures span creation.

func WithSpanKind

func WithSpanKind(kind SpanKind) SpanOption

WithSpanKind returns a SpanOption that sets the span kind.

type SpanStatusCode

type SpanStatusCode int

SpanStatusCode represents the status of a span.

const (
	// SpanStatusUnset is the default status, leaving the span outcome unspecified.
	SpanStatusUnset SpanStatusCode = iota
	// SpanStatusOK marks the span's operation as successful.
	SpanStatusOK
	// SpanStatusError marks the span's operation as failed.
	SpanStatusError
)

type StorageClient

type StorageClient interface {
	// Upload stores an object with the given key in the specified bucket.
	Upload(
		ctx context.Context,
		bucket, key string,
		body io.Reader,
		contentType string,
	) error

	// Download retrieves an object by key from the specified bucket.
	Download(ctx context.Context, bucket, key string) (io.ReadCloser, error)

	// Delete removes an object by key from the specified bucket.
	Delete(ctx context.Context, bucket, key string) error

	// DeleteBatch removes multiple objects by key in a single request. It is the
	// efficient path for cleanup operations (e.g. orphaned-file reconciliation).
	DeleteBatch(ctx context.Context, bucket string, keys []string) error

	// Exists checks if an object exists at the given key.
	Exists(ctx context.Context, bucket, key string) (bool, error)

	// Stat returns metadata (content-type, size, etag, last-modified) for an object
	// without downloading its body. Returns errors.ErrNotFound when the key is absent.
	Stat(ctx context.Context, bucket, key string) (StorageObject, error)

	// PresignURL generates a pre-signed URL for temporary GET (download) access to
	// an object. The URL expires after the specified duration (in seconds).
	//
	// When displayFilename is non-empty the URL is signed with a
	// response-content-disposition of `attachment; filename="…"`, so S3 returns that
	// header on the GET and the browser downloads the object (under the given name)
	// instead of rendering it inline. An empty displayFilename leaves the disposition
	// unset (inline). The override is part of the SigV4 signature, so it must be set
	// here at presign time — a client cannot append it to the URL after the fact.
	PresignURL(
		ctx context.Context,
		bucket, key string,
		expirySeconds int,
		displayFilename string,
	) (string, error)

	// PresignPutURL generates a pre-signed URL for temporary PUT (upload) access,
	// allowing direct browser-to-storage uploads. The URL expires after the
	// specified duration (in seconds); contentType pins the upload's Content-Type.
	PresignPutURL(
		ctx context.Context,
		bucket, key string,
		expirySeconds int,
		contentType string,
	) (string, error)

	// CreateMultipartUpload begins a browser-direct multipart upload and returns its
	// S3 upload id. The caller presigns each part with PresignUploadPart and later
	// finalizes with CompleteMultipartUpload (or discards with AbortMultipartUpload).
	CreateMultipartUpload(
		ctx context.Context,
		bucket, key, contentType string,
	) (string, error)

	// PresignUploadPart generates a pre-signed PUT URL for one part (1-based
	// partNumber) of the multipart upload identified by uploadID. The URL expires
	// after expirySeconds; the browser PUTs the part bytes to it and reads the
	// returned ETag (which the bucket CORS must expose).
	PresignUploadPart(
		ctx context.Context,
		bucket, key, uploadID string,
		partNumber int32,
		expirySeconds int,
	) (string, error)

	// CompleteMultipartUpload finalizes the multipart upload identified by uploadID,
	// assembling the client-reported parts into the object at key. Parts must be
	// ordered by ascending part number.
	CompleteMultipartUpload(
		ctx context.Context,
		bucket, key, uploadID string,
		parts []CompletedPart,
	) error

	// AbortMultipartUpload discards the in-progress multipart upload identified by
	// uploadID, releasing its already-uploaded parts.
	AbortMultipartUpload(ctx context.Context, bucket, key, uploadID string) error

	// ListMultipartUploads lists the in-progress (not yet completed or aborted)
	// multipart uploads in a bucket, so a cleanup sweep can abort abandoned ones —
	// which are invisible to ListObjects until completed.
	ListMultipartUploads(ctx context.Context, bucket string) ([]MultipartUpload, error)

	// ListObjects lists objects in a bucket with the given prefix.
	ListObjects(ctx context.Context, bucket, prefix string) ([]StorageObject, error)

	// ListObjectsPage lists AT MOST limit objects in a bucket with the given prefix — a bounded
	// single-page variant of ListObjects for callers that need a cheap reachability/count probe
	// rather than a full inventory (e.g. a connector's TestConnection against a large customer
	// bucket). A non-positive limit lists at most one page's worth (backend default).
	ListObjectsPage(
		ctx context.Context,
		bucket, prefix string,
		limit int,
	) ([]StorageObject, error)

	// ListObjectsPageToken lists AT MOST limit objects under prefix STARTING AFTER continuationToken
	// (empty token = start of the listing) and returns the objects plus the token to resume from
	// (nextToken == "" when the listing is exhausted). It is the resumable, memory-bounded streaming
	// primitive a large-bucket crawl pages over — never materializing the full key list — so a caller
	// (e.g. a sync engine) holds at most one page at a time and can checkpoint the token to
	// crash-resume. A non-positive limit lets the backend apply its default page size.
	ListObjectsPageToken(
		ctx context.Context,
		bucket, prefix, continuationToken string,
		limit int,
	) (objects []StorageObject, nextToken string, err error)

	// EnsureBucket creates the named bucket if it does not already exist,
	// idempotently (a concurrently-created bucket is treated as success). It exists
	// so local/dev provisioning — e.g. a seeding tool — can guarantee an upload
	// path has its bucket before the first presigned PUT.
	EnsureBucket(ctx context.Context, bucket string) error
}

StorageClient provides object storage operations (S3-compatible): upload (single-part or transparent multipart by size), download, delete (single and batch), existence and metadata checks, presigned GET/PUT URLs, prefix listing, and the presigned/browser-direct multipart control plane (create/presign-part/complete/ abort/list) for large-file uploads. Absent objects surface as errors.ErrNotFound.

type StorageObject

type StorageObject struct {
	// Key is the object's storage key (path) in the bucket.
	Key string `json:"key"`

	// ContentType is the MIME type of the object.
	ContentType string `json:"content_type"`

	// LastModified is the timestamp of the last modification.
	LastModified string `json:"last_modified"`

	// ETag is the entity tag for cache validation.
	ETag string `json:"etag"`

	// Size is the object size in bytes.
	Size int64 `json:"size"`
}

StorageObject represents metadata about a stored object.

type Store

type Store[T any, P any, ID comparable] interface {
	// Reader provides Get (retrieve by id).
	Reader[T, ID]
	// Lister provides List (paginated query by params).
	Lister[T, P]
	// Writer provides Create and Update.
	Writer[T, ID]
	// Deleter provides Delete (remove by id).
	Deleter[ID]
	// Exister provides Exists (existence check by id).
	Exister[ID]
}

Store defines the low-level data access interface that concrete database adapters implement. It has the same CRUD surface as Repository but represents the infrastructure boundary (e.g., Ent, sqlc, pgx) rather than the business-logic boundary. It is composed from the role interfaces, so a full-CRUD adapter embeds the whole set while a read-only or append-only adapter can embed only the roles it honors.

T = entity type, P = query/filter params, ID = identifier type.

type ToolSpec

type ToolSpec interface {
	// Name returns the display name (e.g., "golangci-lint").
	Name() string

	// Binary returns the executable name on PATH (e.g., "golangci-lint").
	Binary() string

	// InstallHint returns installation instructions.
	InstallHint() string

	// Cmd builds the full command: (executable, args) with prefix handling.
	// For tools with a prefix (e.g., "uv run ruff"), Cmd("check", ".")
	// returns ("uv", ["run", "ruff", "check", "."]).
	Cmd(args ...string) (exe string, cmdArgs []string)
}

ToolSpec describes an external CLI tool requirement. It captures the tool's identity, availability check, and command construction.

type Tracer

type Tracer interface {
	// Start creates a new span and returns the updated context and span.
	Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span)

	// Shutdown flushes and shuts down the tracer.
	Shutdown(ctx context.Context) error
}

Tracer provides distributed tracing capabilities.

Implementations: OpenTelemetry (default), or any tracing backend that supports span creation and context propagation.

All foundation and service code depends on this interface, never on a concrete tracing library. Swap implementations via the tracer.New factory.

type Transaction

type Transaction interface {
	// Commit commits the transaction.
	Commit() error

	// Rollback aborts the transaction.
	Rollback() error
}

Transaction represents a database transaction.

type TransactionManager

type TransactionManager interface {
	// Begin starts a new transaction and returns a context with the transaction attached.
	Begin(ctx context.Context) (context.Context, Transaction, error)

	// WithTransaction executes fn within a transaction, automatically committing
	// on success or rolling back on error.
	WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}

TransactionManager handles transaction lifecycle.

type Updater

type Updater[T any, ID comparable] interface {
	// Update modifies an existing entity identified by id.
	Update(ctx context.Context, id ID, entity *T) (*T, error)
}

Updater is the standalone update-only write role: a single Update that modifies an existing entity. It is the mirror of Inserter for the read-write side — a resource whose creation happens through a bespoke path (not the generic Create) but whose updates are the plain keyed mutation — e.g. a membership created only from the identity provider's claims on first sign-in, never through the generic Create, but updated by id. Like Inserter, it is NOT part of the Store/Repository/Service composition (those embed Writer, which carries both Create and Update); it is the role a create-less-but-updatable resource embeds in place of Writer.

type Validatable

type Validatable interface {
	// Validate reports whether the DTO satisfies its field + cross-field rules, returning the
	// first violation as a coded (CodeInvalidInput) error, or nil when every rule is satisfied.
	Validate() error
}

Validatable is a DTO that can validate itself — the below-transport-edge seam the service chokepoint enforces. Its Validate() returns a core/errors.AppError carrying CodeInvalidInput (ARCHITECTURE.md#error-codes) on violation, or nil when the DTO satisfies its rules. The generated per-DTO Validate() (protoc-gen-dtovalidate) satisfies this interface, composing the field checks with an optional hand-written domainRules() hook.

type Worker

type Worker interface {
	// Work executes a job with the given arguments.
	Work(args map[string]interface{}) error
}

Worker processes background jobs of a specific kind.

type WorkerRegistry

type WorkerRegistry interface {
	// Register associates a worker with a job kind.
	Register(kind string, worker Worker)

	// Get retrieves the worker registered for the given kind.
	Get(kind string) (Worker, bool)
}

WorkerRegistry registers and retrieves job workers by kind.

type Workflow

type Workflow[In any, Out any] interface {
	// Execute orchestrates a multi-step business process.
	// Context is provided for cancellation and tracing.
	// Implementations may be stateful and typically depend on services, repos, and caches.
	Execute(ctx context.Context, input In) (Out, error)
}

Workflow is the generic business process orchestration interface for multi-step coordination. In = input type, Out = output type.

Workflows coordinate multiple operations across services, pipelines, repositories, and caches. Unlike pipelines (stateless transforms), workflows orchestrate stateful business processes with conditional logic, error recovery, and cross-cutting concerns.

Examples: a lookup workflow (check cache → run pipeline → cache result), ingestion workflow (parse → validate → transform → index), order fulfillment (verify → charge → ship → notify).

type Writer

type Writer[T any, ID comparable] interface {
	// Create persists a new entity and returns it with generated fields populated.
	Create(ctx context.Context, entity *T) (*T, error)

	// Update modifies an existing entity identified by id.
	Update(ctx context.Context, id ID, entity *T) (*T, error)
}

Writer creates and updates entities.

Jump to

Keyboard shortcuts

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