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 ¶
- func InTx(ctx context.Context) bool
- func RegisterAfterCommit(ctx context.Context, fn func())
- func RunAfterCommit(ctx context.Context)
- func WithAfterCommit(ctx context.Context) context.Context
- func WithInTx(ctx context.Context) context.Context
- type AdaptiveThrottler
- type AnalyticsCompactor
- type AnalyticsStore
- type AnyJob
- type BufferedMessage
- type Bulkhead
- type ByteCache
- type Cache
- type CacheInvalidator
- type CircuitBreaker
- type Client
- type CmdHook
- type CmdResult
- type CommandRunner
- type CompletedPart
- type ConfigLoader
- type ConnectorKind
- type ConnectorSource
- type Counter
- type CountingPublisher
- type DatabasePool
- type DeadLetter
- type DeadLetterBackend
- type DecoratedRepository
- type DecoratedService
- type Deduplicator
- type Deleter
- type Discoverer
- type DiscovererFunc
- type DistributedLock
- type DynamicConsumer
- type Encryptor
- type EventPublisher
- type ExecutionObserver
- type Exister
- type FS
- type Gauge
- type GitOps
- type HealthChecker
- type Hedger
- type Histogram
- type Inserter
- type Job
- type JobEnqueuer
- type JobFactory
- type JobScheduler
- type LeaderElector
- type Lifecycle
- type ListRunner
- type Lister
- type Locker
- type Logger
- type Message
- type MessageConsumer
- type MessageHandler
- type MessagePublisher
- type Metrics
- type MetricsQuerier
- type MultipartUpload
- type Named
- type OpService
- type OutboxSink
- type OutboxStore
- type PatternConsumer
- type PeriodicJob
- type Pipeline
- type QueryBackend
- type RateLimiter
- type Reader
- type ReplayBuffer
- type Repository
- type Retrier
- type RowStream
- type Runnable
- type ServerTransport
- type Service
- type ServiceHooks
- type ServiceTokenValidator
- type Source
- type SourceBuilder
- type SourceConfig
- type Span
- type SpanConfig
- type SpanKind
- type SpanOption
- type SpanStatusCode
- type StorageClient
- type StorageObject
- type Store
- type ToolSpec
- type Tracer
- type Transaction
- type TransactionManager
- type Updater
- type Validatable
- type Worker
- type WorkerRegistry
- type Workflow
- type Writer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InTx ¶
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 ¶
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 ¶
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 ¶
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.
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 AnalyticsCompactor ¶ added in v0.3.2
type AnalyticsCompactor interface {
// Compact merges the per-fact rows of org's cube at grain in the bucket that
// starts at bucket.
Compact(
ctx context.Context,
cube string,
grain types.Grain,
org string,
bucket time.Time,
) error
}
AnalyticsCompactor is optionally implemented by an AnalyticsStore that keeps one row per fact: Compact merges a closed bucket's per-fact rows into one row per group, preserving every answer. It is idempotent and safe to rerun.
type AnalyticsStore ¶ added in v0.3.2
type AnalyticsStore interface {
// Lifecycle starts and stops the store's connections.
Lifecycle
// Aggregate opens a stream of the partial rows answering q. q.OrgID scopes the
// read and q.ResumeToken, when set, resumes a previous stream after its last
// delivered page.
Aggregate(ctx context.Context, q types.AggregateQuery) (RowStream, error)
// Write upserts facts; writing the same fact twice leaves the store unchanged.
Write(ctx context.Context, facts []types.Fact) error
}
AnalyticsStore persists analytics facts and answers aggregate queries over them. Writes are idempotent per Fact.IdempotencyKey (a redelivered fact is a no-op), so a consumer may retry them freely. Aggregate returns a pull-based RowStream: rows are fetched a page at a time as the caller asks, so a slow reader slows the store instead of growing memory.
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 ¶
DiscovererFunc is a function that implements Discoverer[T].
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 ¶
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 MetricsQuerier ¶ added in v0.3.2
type MetricsQuerier interface {
// Query evaluates query as an instant query and returns its single sample value.
Query(ctx context.Context, query string) (float64, error)
// QueryRange evaluates query over [start, end] at step and returns its single
// series; no series is an empty series.
QueryRange(
ctx context.Context,
query string,
start, end time.Time,
step time.Duration,
) ([]types.MetricSample, error)
}
MetricsQuerier evaluates metric queries (PromQL) against a metrics backend. A backend that measures fails closed: a value it can't measure is an error, never a silent zero. The zero-infrastructure stub backend is the deliberate exception: it measures nothing and answers zero and an empty series so the graph boots without a metrics server.
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 OutboxSink ¶ added in v0.3.2
type OutboxSink interface {
// Send delivers recs and returns exactly one result per record, in order: nil
// for a delivered record, the coded delivery error otherwise.
Send(ctx context.Context, recs []types.OutboxRecord) []error
}
OutboxSink delivers claimed outbox records to their destination (a queue, an object store, …).
type OutboxStore ¶ added in v0.3.2
type OutboxStore interface {
// Claim leases up to limit pending rows of lane whose next attempt is due (on
// the database clock) with FOR UPDATE SKIP LOCKED semantics, so concurrent
// claimers receive disjoint rows. It counts the attempt (the returned Attempts
// includes it) and is side-effecting, so a caller must not blindly retry it.
Claim(ctx context.Context, lane string, limit int) ([]types.OutboxRecord, error)
// MarkSent records that the row was delivered; it is never claimed again.
MarkSent(ctx context.Context, id uuid.UUID) error
// Retry makes the row claimable again at next, recording lastErr.
Retry(ctx context.Context, id uuid.UUID, next time.Time, lastErr string) error
// Park takes the row out of rotation after its final failed attempt, recording lastErr.
Park(ctx context.Context, id uuid.UUID, lastErr string) error
// Stats summarizes lane's pending and parked rows.
Stats(ctx context.Context, lane string) (types.OutboxStats, error)
}
OutboxStore is the durable side of a transactional outbox: the table the application writes rows into within its business transaction, and that the outbox relay leases, finalizes and summarizes.
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 RowStream ¶ added in v0.3.2
type RowStream interface {
// Next returns the next page of rows and whether more pages may follow; a page
// may be empty (every row filtered out) while more is still true.
Next(ctx context.Context) (rows []types.Row, more bool, err error)
// ResumeToken is an opaque position after the last page Next returned; pass it
// as AggregateQuery.ResumeToken to continue the stream elsewhere.
ResumeToken() []byte
// Close releases the stream's resources; it is safe to call more than once.
Close() error
}
RowStream is a pull-based, page-at-a-time result of AnalyticsStore.Aggregate.
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.
Source Files
¶
- adaptive_throttler.go
- analytics.go
- auth.go
- bulkhead.go
- byte_cache.go
- cache.go
- circuit_breaker.go
- config.go
- connector.go
- crypto.go
- database.go
- deadletter.go
- dedup.go
- discovery.go
- events.go
- exec_job.go
- factory.go
- fs.go
- git_ops.go
- hedger.go
- jobs.go
- leader.go
- lifecycle.go
- list_runner.go
- lock.go
- logger.go
- messaging.go
- metrics.go
- metrics_query.go
- observer.go
- op_service.go
- outbox.go
- pipeline.go
- query_backend.go
- rate_limiter.go
- replaybuffer.go
- repository.go
- retrier.go
- roles.go
- runner.go
- secrets.go
- service.go
- storage.go
- store.go
- tool.go
- tracer.go
- transport.go
- validatable.go
- workflow.go