Documentation
¶
Overview ¶
Package framework is the public surface of the GoFastr framework.
It contains the App spine — App, Plugin, Registry, lifecycle, typed hooks, the in-memory test harness — plus thin re-exports of every subpackage's public API so callers can keep writing framework.Entity, framework.NewCrudHandler, framework.AutoMigrate, etc.
The actual implementations live in subpackages:
- framework/entity Entity model, columns, relations, validators
- framework/crud HTTP CRUD handler, eager loading, includes, typed query, MCP tool generator
- framework/hook HookRegistry + lifecycle constants
- framework/event EventBus + Event types
- framework/migrate AutoMigrate + DiffSchema + Dialect detection
- framework/openapi EntityOpenAPI spec generator
- framework/dsl ?dsl= query parser
- framework/filter query-string filter & sort parsing
- framework/pagination cursor + offset paging
- framework/tenant multi-tenancy (TenantConfig, TenantMiddleware)
- framework/softdelete soft-delete helpers
- framework/access RBAC (Permission, Policy, RolePolicy)
- framework/file FileField upload helpers
- framework/cron in-process cron scheduler
- framework/slowquery SlowQueryLogger DBExecutor wrapper
- framework/db shared Executor + tx context primitives
Both surfaces are first-class: the facade re-exports give callers short, one-import access (`framework.Entity`, `framework.AutoMigrate`), while the narrow subpackages give plugin authors and codegen tools a precise dependency graph.
See framework/ARCHITECTURE.md for the layering rules, cycle-breaking interfaces, and the recipe for extracting a new subpackage.
Index ¶
- Constants
- Variables
- func AppendAuditEvent(ctx context.Context, db *sql.DB, table, entity, op, recordID, actorID string, ...) error
- func AssertRow(res sql.Result, module string) error
- func ComputeSurfaceSHA256(d ProcessModuleDescriptor) (string, error)
- func DBFromContext(ctx context.Context) (*sql.DB, bool)
- func DefaultChildEnvAllowlist() []string
- func DefaultMiddleware(a *App) []router.Middleware
- func EnsureAuditTable(db *sql.DB, table string) error
- func GetAs[T any](bm *BatteryManager, name string) (T, error)
- func ModuleToolDigest(tool moduleproto.Tool) string
- func NewTypedQuery[T any](h *crud.CrudHandler) *crud.TypedQuery[T]
- func OnAfterCreate[T any](app *App, name string, fn func(ctx context.Context, value *T) error)
- func OnAfterDelete(app *App, name string, fn func(ctx context.Context, id string) error)
- func OnAfterGet(app *App, name string, fn func(ctx context.Context, p *hook.GetPayload) error)
- func OnAfterList(app *App, name string, fn func(ctx context.Context, p *hook.ListPayload) error)
- func OnAfterUpdate[T any](app *App, name string, fn func(ctx context.Context, value *T) error)
- func OnBeforeCreate[T any](app *App, name string, fn func(ctx context.Context, value *T) error)
- func OnBeforeDelete(app *App, name string, fn func(ctx context.Context, id string) error)
- func OnBeforeGet(app *App, name string, fn func(ctx context.Context, p *hook.GetPayload) error)
- func OnBeforeList(app *App, name string, fn func(ctx context.Context, p *hook.ListPayload) error)
- func OnBeforeUpdate[T any](app *App, name string, fn func(ctx context.Context, value *T) error)
- func PluginGetAs[T any](pm *PluginManager, name string) (T, error)
- func TxFromContext(ctx context.Context) (*sql.Tx, bool)
- func ValidateProcessModuleDescriptor(d ProcessModuleDescriptor, approved ApprovedGrants) ([]access.Permission, error)
- func WithDBContext(ctx context.Context, db *sql.DB) context.Context
- type ACPConfig
- type AccessControl
- type AccessDeclaration
- type AgentAuthBlock
- type AgentSkillEntry
- type App
- func (a *App) AddCron(s *cron.Scheduler) *App
- func (a *App) AddQueue(q schedulerStartStop) *App
- func (a *App) CrudHandler(name string) (*crud.CrudHandler, error)
- func (a *App) DBContextMiddleware() router.Middleware
- func (a *App) Entity(name string, config entity.EntityConfig) *App
- func (a *App) Events() *event.EventBus
- func (a *App) ExportData(ctx context.Context, dir string, opts ...ExportOption) error
- func (a *App) ExportStatic(ctx context.Context, dir, basePath string) error
- func (a *App) Flags() *featureflag.Evaluator
- func (a *App) Group(prefix string, opts ...routegroup.GroupOption) *routegroup.RouteGroup
- func (a *App) GroupEntity(g *routegroup.RouteGroup, name string, config entity.EntityConfig) *App
- func (a *App) HookRegistry(entityName string) *hook.HookRegistry
- func (a *App) ImportData(ctx context.Context, dir string) error
- func (a *App) InTx(ctx context.Context, fn func(ctx context.Context, tx *sql.Tx) error) error
- func (a *App) InitPlugins() error
- func (a *App) IsEnabled(ctx context.Context, key string) bool
- func (a *App) JSONCasing() crud.JSONCase
- func (a *App) Lifecycle() *lifecycle.Lifecycle
- func (a *App) Logger() *slog.Logger
- func (a *App) Modules() *ModuleManager
- func (a *App) Mount(m Mountable) *App
- func (a *App) Mountables() []Mountable
- func (a *App) MustCrudHandler(name string) *crud.CrudHandler
- func (a *App) NewModuleMigrationCoordinator(opts ...CoordinatorOption) (*MigrationCoordinator, error)
- func (a *App) OnReady(fn func(addr string)) *App
- func (a *App) OnStart(fn func(ctx context.Context) error) *App
- func (a *App) OnStop(fn func() error) *App
- func (a *App) OnStopFirst(fn func() error) *App
- func (a *App) Outbox() *outbox.Outbox
- func (a *App) ProcessModules() *ProcessModuleSupervisor
- func (a *App) RegisterBattery(b Battery, deps ...string) *App
- func (a *App) RegisterEntities(entities map[string]entity.EntityConfig) *App
- func (a *App) RegisterModule(m Module) *App
- func (a *App) RegisterPlugin(plugin Plugin) *App
- func (a *App) RegisterProcessModule(desc ProcessModuleDescriptor, approved ApprovedGrants) *App
- func (a *App) RegisterReadiness(name string, check func(ctx context.Context) error) *App
- func (a *App) Role() Role
- func (a *App) Router() *router.Router
- func (a *App) Routine(r migrate.Routine) *App
- func (a *App) RoutinesFS(fsys fs.FS, dir string) *App
- func (a *App) RunReadinessChecks(ctx context.Context) ReadinessResponse
- func (a *App) RunWithSignals(ctx context.Context) error
- func (a *App) SetFlagStore(s featureflag.Store) *App
- func (a *App) SetLogger(l *slog.Logger)
- func (a *App) Shutdown(ctx context.Context) error
- func (a *App) Start(addr string) error
- func (a *App) T(ctx context.Context, key string, params ...map[string]any) string
- func (a *App) Table(t migrate.Table) *App
- func (a *App) Translator() *i18n.Translator
- func (a *App) TryEntity(name string, config entity.EntityConfig) (err error)
- func (a *App) Use(mw ...router.Middleware) *App
- func (a *App) View(v migrate.View) *App
- func (a *App) WithAuditLog(cfg AuditConfig) *App
- func (a *App) WithSeed(fn func(ctx context.Context) error) *App
- type AppConfig
- type AppOption
- func WithACP(cfg ACPConfig) AppOption
- func WithAPIPrefix(prefix string) AppOption
- func WithAgentSkills(skills []AgentSkillEntry) AppOption
- func WithAuthMD(cfg AuthMDConfig) AppOption
- func WithConfig(config AppConfig) AppOption
- func WithDB(db *sql.DB) AppOption
- func WithFanout(f fanout.Fanout) AppOption
- func WithFileStorage(s upload.Storage) AppOption
- func WithGrantStore(gs *access.GrantStore) AppOption
- func WithI18n(tr *i18n.Translator) AppOption
- func WithIdempotency(cfg middleware.IdempotencyConfig) AppOption
- func WithLocaleResolver(f func(*http.Request) (string, bool)) AppOption
- func WithLogger(l *slog.Logger) AppOption
- func WithMCP() AppOption
- func WithMCPApp(cfg mcp.AppConfig) AppOption
- func WithMCPControl() AppOption
- func WithMCPIntrospection() AppOption
- func WithMCPServer(s *mcp.Server) AppOption
- func WithMetrics() AppOption
- func WithOAuthAuthorizationServer(cfg OAuthAuthorizationServerConfig) AppOption
- func WithOAuthProtectedResource(cfg OAuthProtectedResourceConfig) AppOption
- func WithOutbox(opts ...outbox.Option) AppOption
- func WithOutboxConsumer(name, eventType string, handler event.EventHandler) AppOption
- func WithPublicOpenAPI() AppOption
- func WithReadinessTimeout(d time.Duration) AppOption
- func WithRole(r Role) AppOption
- func WithRouter(r *router.Router) AppOption
- func WithSecret(secret string) AppOption
- func WithSecurityHeaders(cfg middleware.SecurityHeadersConfig) AppOption
- func WithSetup(r SetupRunner) AppOption
- func WithTracing() AppOption
- func WithUCP(cfg UCPConfig) AppOption
- func WithVerboseReadiness() AppOption
- func WithWebBotAuth(cfg WebBotAuthConfig) AppOption
- func WithoutAutoMigrate() AppOption
- func WithoutDefaultMiddleware() AppOption
- type ApplyOptions
- type ApprovedGrants
- type ApprovedMigration
- type AuditConfig
- type AuthMDConfig
- type Battery
- type BatteryLifecycle
- type BatteryManager
- func (bm *BatteryManager) All() []Battery
- func (bm *BatteryManager) Get(name string) (Battery, error)
- func (bm *BatteryManager) InitAll(app *App) error
- func (bm *BatteryManager) Names() []string
- func (bm *BatteryManager) Register(b Battery, deps ...string) error
- func (bm *BatteryManager) StartAll(ctx context.Context) error
- func (bm *BatteryManager) StopAll(ctx context.Context) error
- type BoolColumn
- type Broker
- type BrokerOption
- type CachedResolver
- type ChildSpec
- type Column
- type Condition
- type ConformanceReport
- type CoordinatorOption
- type CoordinatorPlan
- type CoordinatorValidationError
- type CronJob
- type CrudHandler
- type DBExecutor
- type Decider
- type Decision
- type DescriptorValidationError
- type DesiredState
- type DestructiveChangeError
- type Dialect
- type Endpoint
- type Entity
- type EntityConfig
- type EntityDeclaration
- type Event
- type EventBus
- type EventHandler
- type ExecutableSHAMismatchError
- type ExportOption
- type FieldDeclaration
- type FloatColumn
- type ForeignKey
- type GrantStore
- type GroupOption
- type HookFunc
- type HookGetPayload
- type HookListPayload
- type HookRegistry
- type HookType
- type HookWhereClause
- type InMemoryModuleStore
- type IncludeNode
- type Index
- type IntColumn
- type JSONCase
- type ListOptions
- type ListResponse
- type Metrics
- type MigrationCoordinator
- type MigrationPlan
- type Module
- type ModuleGrantView
- type ModuleInfo
- type ModuleLimits
- type ModuleManager
- func (mm *ModuleManager) Disable(ctx context.Context, name string) error
- func (mm *ModuleManager) Enable(ctx context.Context, name string) error
- func (mm *ModuleManager) Enabled(name string) bool
- func (mm *ModuleManager) List() []ModuleInfo
- func (mm *ModuleManager) SetProcessCoordinator(pc processModuleCoordinator)
- type ModuleManifest
- type ModuleStore
- type ModuleToolRegistry
- func (r *ModuleToolRegistry) GateForModule(moduleName string) error
- func (r *ModuleToolRegistry) ModuleFor(toolName string) (string, bool)
- func (r *ModuleToolRegistry) RegisterTools(moduleName string, tools []moduleproto.Tool) error
- func (r *ModuleToolRegistry) UnregisterTools(moduleName string) error
- type Mountable
- type NopBroker
- type OAuthAuthorizationServerConfig
- type OAuthProtectedResourceConfig
- type Order
- type Permission
- type PlannedStep
- type Plugin
- type PluginManager
- type Policy
- type ProbeID
- type ProbeResult
- type ProbeStatus
- type ProcessModuleDescriptor
- type ProcessModuleInfo
- type ProcessModuleStore
- type ProcessModuleSupervisor
- func (s *ProcessModuleSupervisor) BumpGeneration(ctx context.Context, name string) (uint64, error)
- func (s *ProcessModuleSupervisor) Close(ctx context.Context) error
- func (s *ProcessModuleSupervisor) Closed() bool
- func (s *ProcessModuleSupervisor) DeclaredRoutes(name string) []RouteDeclaration
- func (s *ProcessModuleSupervisor) Disable(ctx context.Context, name string) error
- func (s *ProcessModuleSupervisor) Drain(ctx context.Context) error
- func (s *ProcessModuleSupervisor) Enable(ctx context.Context, name string) error
- func (s *ProcessModuleSupervisor) Info(name string) (ProcessModuleInfo, error)
- func (s *ProcessModuleSupervisor) List() []ProcessModuleInfo
- func (s *ProcessModuleSupervisor) ProxyHandler(name, routeID string) http.Handler
- func (s *ProcessModuleSupervisor) Reconcile(name string)
- func (s *ProcessModuleSupervisor) Register(ctx context.Context, d ProcessModuleDescriptor, approved ApprovedGrants) (RegisterResult, error)
- func (s *ProcessModuleSupervisor) RevokeGrants(ctx context.Context, name string, grants []access.Permission) (uint64, error)
- func (s *ProcessModuleSupervisor) Slot(name string) *moduleSlot
- func (s *ProcessModuleSupervisor) StartLoops()
- type ProcessState
- type ReadinessCheck
- type ReadinessRegistrar
- type ReadinessResponse
- type ReadinessResult
- type Ref
- type RegisterResult
- type Registry
- type Relation
- type RelationType
- type ReplicaHeartbeat
- type ReverseBroker
- type Role
- type RolePolicy
- type RoleWithOrigin
- type RouteDeclaration
- type RouteGroup
- type Routine
- type Runner
- type RunningChild
- type SQLModuleStore
- type SQLProcessModuleStore
- func (s *SQLProcessModuleStore) BumpGeneration(ctx context.Context, module string) (uint64, error)
- func (s *SQLProcessModuleStore) DeleteHeartbeat(ctx context.Context, module, replicaID string) error
- func (s *SQLProcessModuleStore) Dialect() migrate.Dialect
- func (s *SQLProcessModuleStore) EnsureSchema(ctx context.Context) error
- func (s *SQLProcessModuleStore) GetDesired(ctx context.Context, module string) (DesiredState, error)
- func (s *SQLProcessModuleStore) Install(ctx context.Context, d DesiredState) error
- func (s *SQLProcessModuleStore) ListDesired(ctx context.Context) ([]DesiredState, error)
- func (s *SQLProcessModuleStore) LiveReplicas(ctx context.Context, module string, ttl time.Duration) ([]ReplicaHeartbeat, error)
- func (s *SQLProcessModuleStore) RecordHeartbeat(ctx context.Context, module, replicaID string, observedGen uint64, ...) error
- func (s *SQLProcessModuleStore) SetEffectiveGrants(ctx context.Context, module string, grants []access.Permission) (uint64, error)
- func (s *SQLProcessModuleStore) SetEnabled(ctx context.Context, module string, enabled bool) error
- func (s *SQLProcessModuleStore) SetMigrationsAppliedAt(ctx context.Context, module string, at *time.Time) error
- type SandboxBackend
- type SandboxOpts
- type SandboxRunner
- type Scheduler
- type SchemaChange
- type SchemaSnapshot
- type SetupRunner
- type StringColumn
- type SupervisorConfig
- type Table
- type TenantConfig
- type TestApp
- func (ta *TestApp) AsUser(user any) *TestApp
- func (ta *TestApp) Close()
- func (ta *TestApp) Delete(path string) *TestResponse
- func (ta *TestApp) Get(path string) *TestResponse
- func (ta *TestApp) Post(path string, body any) *TestResponse
- func (ta *TestApp) Put(path string, body any) *TestResponse
- func (ta *TestApp) Request(method, path string, body io.Reader) *TestRequest
- type TestRequest
- type TestResponse
- func (tr *TestResponse) AssertBodyContains(t testing.TB, substr string) *TestResponse
- func (tr *TestResponse) AssertHeader(t testing.TB, key, expected string) *TestResponse
- func (tr *TestResponse) AssertJSON(t testing.TB, expected any) *TestResponse
- func (tr *TestResponse) AssertStatus(t testing.TB, expected int) *TestResponse
- func (tr *TestResponse) Body() string
- func (tr *TestResponse) Close()
- func (tr *TestResponse) JSON(v any) error
- func (tr *TestResponse) Status() int
- type TimestampColumn
- type ToolDigest
- type ToolRegistrar
- type TrustTier
- type TrustedProcessRunner
- type TypedQuery
- type UCPConfig
- type UUIDColumn
- type UnknownCapabilityError
- type UntrustedNoSandboxError
- type ValidationError
- type ValidationRegistry
- type ValidatorFunc
- type View
- type WebBotAuthConfig
Constants ¶
const ( DecisionAbstain = access.DecisionAbstain DecisionAllow = access.DecisionAllow DecisionDeny = access.DecisionDeny )
const ( CaseCamel = crud.CaseCamel CaseSnake = crud.CaseSnake MaxBatchSize = crud.MaxBatchSize MaxMultipartMemory = crud.MaxMultipartMemory )
const ( RelHasOne = entity.RelHasOne RelHasMany = entity.RelHasMany RelManyToOne = entity.RelManyToOne RelManyToMany = entity.RelManyToMany )
const ( EntityCreated = event.EntityCreated EntityUpdated = event.EntityUpdated EntityDeleted = event.EntityDeleted )
const ( BeforeCreate = hook.BeforeCreate AfterCreate = hook.AfterCreate BeforeUpdate = hook.BeforeUpdate AfterUpdate = hook.AfterUpdate BeforeDelete = hook.BeforeDelete AfterDelete = hook.AfterDelete BeforeList = hook.BeforeList AfterList = hook.AfterList BeforeGet = hook.BeforeGet AfterGet = hook.AfterGet )
const ( DialectPostgres = migrate.DialectPostgres DialectSQLite = migrate.DialectSQLite )
Variables ¶
var ( NewRolePolicy = access.NewRolePolicy NewGrantStore = access.NewGrantStore RequirePermission = access.RequirePermission GetPermissions = access.GetPermissions WithPolicy = access.WithPolicy WithRoles = access.WithRoles // GetRoles reads the roles installed via WithRoles back out of the // request context — the reader half of the role-context seam, for // role-based UI branching. GetRoles = access.GetRoles // Can reports whether the request context carries a permission. Can = access.Can // AccessMiddleware installs the RBAC policy + roles into request context // so RequirePermission and EntityConfig.Access gates can resolve. AccessMiddleware = access.Middleware // NewCachedResolver wraps a func(ctx) []string roles resolver with // per-user TTL caching + Invalidate; pair with AccessMiddleware. NewCachedResolver = access.NewCachedResolver // WithResolverTTL configures NewCachedResolver's cache TTL. WithResolverTTL = access.WithTTL // CanResource is Can with a resource in hand: it consults the Decider // installed via DeciderMiddleware/WithDecider, falling back to Can. CanResource = access.CanResource // WithDecider / GetDecider install and read the resource-decision seam // on a context; DeciderMiddleware does it per-request after // AccessMiddleware. WithDecider = access.WithDecider GetDecider = access.GetDecider DeciderMiddleware = access.DeciderMiddleware )
var ( NewCrudHandler = crud.NewCrudHandler RegisterCrudRoutes = crud.RegisterCrudRoutes RegisterCrudRoutesFunc = crud.RegisterCrudRoutesFunc MarshalEntity = crud.MarshalEntity UnmarshalEntity = crud.UnmarshalEntity IsNotFound = crud.IsNotFound EagerLoad = crud.EagerLoad RegisterEntityMCPTools = crud.RegisterEntityMCPTools WithServerWrites = crud.WithServerWrites NewValidationError = crud.NewValidationError )
var ( Define = entity.Define HasOne = entity.HasOne HasMany = entity.HasMany BelongsTo = entity.BelongsTo ManyToMany = entity.ManyToMany NewStringColumn = entity.NewStringColumn NewIntColumn = entity.NewIntColumn NewFloatColumn = entity.NewFloatColumn NewBoolColumn = entity.NewBoolColumn NewTimestampColumn = entity.NewTimestampColumn NewUUIDColumn = entity.NewUUIDColumn NewValidationRegistry = entity.NewValidationRegistry Required = entity.Required Unique = entity.Unique Custom = entity.Custom FormatValidationErrors = entity.FormatValidationErrors And = entity.And Or = entity.Or Not = entity.Not )
var ( AutoMigrate = migrate.AutoMigrate AutoMigrateContext = migrate.AutoMigrateContext AutoMigratePlanContext = migrate.AutoMigratePlanContext MigrateEntity = migrate.MigrateEntity MigrateEntityDialect = migrate.MigrateEntityDialect DiffSchema = migrate.DiffSchema ApplySchemaDiff = migrate.ApplySchemaDiff ApplySchemaDiffWithOptions = migrate.ApplySchemaDiffWithOptions DetectDialect = migrate.DetectDialect GenerateMigration = migrate.GenerateMigration GeneratePlan = migrate.GeneratePlan SnapshotFromRegistry = migrate.SnapshotFromRegistry SnapshotFromPlan = migrate.SnapshotFromPlan RenderMigrationFile = migrate.RenderMigrationFile LoadSnapshot = migrate.LoadSnapshot SaveSnapshot = migrate.SaveSnapshot )
var ( NewMetrics = middleware.NewMetrics MetricsMiddleware = middleware.MetricsMiddleware MetricsHandler = middleware.MetricsHandler Tracing = middleware.Tracing )
var ( DefaultTenantConfig = tenant.DefaultTenantConfig WithMultiTenant = tenant.WithMultiTenant ApplyTenantFilter = tenant.ApplyTenantFilter TenantMiddleware = tenant.TenantMiddleware SetTenantID = tenant.SetTenantID GetTenantID = tenant.GetTenantID InjectTenantID = tenant.InjectTenantID )
var EntityOpenAPI = openapi.EntityOpenAPI
var ErrModuleInstalled = errors.New("processmodule: module already installed")
ErrModuleInstalled is returned by ProcessModuleStore.Install when a row already exists for the module.
var ErrModuleToolNotReady = errors.New("processmodule: module tool not ready")
ErrModuleToolNotReady is returned by [dispatchToolCall] when a tool is invoked whose module has no live Ready child. It is the retryable companion to the disabled-omit gate; the handler maps it to errModuleToolUnavailable before returning to the MCP server.
var ErrNoDesiredRow = errors.New("processmodule: no desired-state row")
ErrNoDesiredRow is returned by ProcessModuleStore.GetDesired when the module has no desired-state row.
ErrSandboxUnavailable is returned by NewSandboxRunner when the host has no probe-passing sandbox backend. It is the constructor-side twin of UntrustedNoSandboxError: the operator gets one at Register, the supervisor wiring gets the other at construction.
var NewEventBus = event.NewEventBus
var NewHookRegistry = hook.NewHookRegistry
var NewScheduler = cron.NewScheduler
Functions ¶
func AppendAuditEvent ¶ added in v0.14.0
func AppendAuditEvent(ctx context.Context, db *sql.DB, table, entity, op, recordID, actorID string, diff map[string]any) error
AppendAuditEvent appends a non-CRUD audit row (e.g. an auth security event) to the audit table. It reuses the same row writer and the same control-byte sanitisation as the CRUD audit hooks, so a custom sink and the lifecycle hooks cannot drift apart. diff may be nil; an empty map is treated as nil so a no-detail event writes a NULL diff column rather than "{}".
Unlike the CRUD hooks this is NOT transactional by default: it writes through the plain pool unless a transaction is already on ctx (resolved via TxFromContext), because security events (login, 2FA, password reset) are not part of an entity write. The caller owns atomicity if it needs any.
func AssertRow ¶ added in v0.31.0
AssertRow wraps a sql.Result from an UPDATE, translating zero-rows-affected into ErrNoDesiredRow so callers see a single typed error.
func ComputeSurfaceSHA256 ¶ added in v0.31.0
func ComputeSurfaceSHA256(d ProcessModuleDescriptor) (string, error)
ComputeSurfaceSHA256 is the canonicalizer the install path uses to mint a ProcessModuleDescriptor.SurfaceSHA256 from the surface fields (design §3 decision B). The exact bytes are part of the contract: the child must produce the SAME digest from the same surface bytes at handshake. The canonical form is the JSON encoding of surfaceCanonical below, sorted for determinism.
This is exported so the install tool (a later wave) and tests can mint a descriptor's surface digest from the same source the validator reads.
func DBFromContext ¶ added in v0.3.2
DBFromContext returns the *sql.DB stamped onto ctx by an App with a DB configured. The second return value is false when no DB is present — e.g. a UI-only app, or a context that never passed through the App's request chain.
This is the package-portable alternative to the package-level handle idiom: a shared screen can call framework.DBFromContext(ctx) instead of closing over a global *sql.DB captured at main() time.
func DefaultChildEnvAllowlist ¶ added in v0.31.0
func DefaultChildEnvAllowlist() []string
DefaultChildEnvAllowlist is the minimal set of host env-var names a child needs to exec and run ordinary tools, with no secrets. Grows only when a real child genuinely cannot run without a name. Mirrors framework/harness/mcpclient.defaultEnvAllowlist so the two spawners share the same baseline.
func DefaultMiddleware ¶
func DefaultMiddleware(a *App) []router.Middleware
DefaultMiddleware is the framework's standard safety chain in canonical order:
recovery → request-id → [idempotency] → [i18n] → security headers → timeout
The optional entries are present when the App was configured with WithIdempotency / WithI18n; the timeout entry is omitted when AppConfig.DisableRequestTimeout is true.
Access logging is deliberately NOT in this list. battery/log owns structured access logging when registered, and ad-hoc apps that just want a basic line can add middleware.LoggingFn(app.Logger) themselves — having both fire produces duplicate entries with mismatched fields (`request` from the framework, `http.access` from the plugin).
Takes the App so the recovery middleware can route panics through app.Logger (late-binding) and the timeout reflects AppConfig.RequestTimeout. Pass nil only in tests; the recovery falls back to slog.Default and the timeout to 30s.
func EnsureAuditTable ¶
EnsureAuditTable creates the audit_log table if it does not exist. Idempotent. Dialect-aware via the existing migrate.DetectDialect helper.
func GetAs ¶
func GetAs[T any](bm *BatteryManager, name string) (T, error)
GetAs retrieves a battery by name and type-asserts it to T. Returns an error if the battery is not found or doesn't implement T.
func ModuleToolDigest ¶ added in v0.31.0
func ModuleToolDigest(tool moduleproto.Tool) string
ModuleToolDigest is the canonical SHA-256 of one module tool, computed over the same canonical JSON the install tool hashes when it mints a ToolDigest. The handshake byte-compares this against ToolDigest.SHA256; the install tool and tests call this so the bytes agree. The canonical form is the JSON encoding of {id,name,description, input_schema} in that field order — deterministic because struct field order is stable and the child echoes the same shape verbatim.
func NewTypedQuery ¶
func NewTypedQuery[T any](h *crud.CrudHandler) *crud.TypedQuery[T]
func OnAfterCreate ¶
OnAfterCreate registers a typed AfterCreate hook. The callback receives the just-inserted row (already includes server-generated fields like id). Mutations are not reflected — Create has already committed the row's shape; modifying the struct is harmless but pointless.
func OnAfterDelete ¶
OnAfterDelete registers a typed AfterDelete hook. Same shape as OnBeforeDelete.
func OnAfterGet ¶
OnAfterGet registers a typed AfterGet hook. The callback receives *hook.GetPayload with Result populated — mutate the map in place to redact fields before the response is serialised.
func OnAfterList ¶
OnAfterList registers a typed AfterList hook. The callback receives the *hook.ListPayload with Results populated — mutate the slice in place to redact / drop rows.
func OnAfterUpdate ¶
OnAfterUpdate registers a typed AfterUpdate hook receiving the post-update row.
func OnBeforeCreate ¶
OnBeforeCreate registers a typed BeforeCreate hook on the entity named `name`. Mutations the callback makes to *T are reflected back into the pending body so the subsequent INSERT picks them up.
func OnBeforeDelete ¶
OnBeforeDelete registers a typed BeforeDelete hook. The payload is the record id; no generic parameter needed.
func OnBeforeGet ¶
OnBeforeGet registers a typed BeforeGet hook. The callback receives *hook.GetPayload; ID is the request's path-value and AddWhere lets the host scope the lookup (mismatch → 404).
func OnBeforeList ¶
OnBeforeList registers a typed BeforeList hook. The callback receives the framework's *hook.ListPayload directly so callers can append WHERE clauses (p.AddWhere) without type-asserting from any. Symmetric with OnBeforeCreate/OnBeforeUpdate.
func OnBeforeUpdate ¶
OnBeforeUpdate registers a typed BeforeUpdate hook. *T is sparse — it holds whatever the caller sent, not the full row.
func PluginGetAs ¶ added in v0.3.2
func PluginGetAs[T any](pm *PluginManager, name string) (T, error)
PluginGetAs retrieves a plugin by name and type-asserts it to T. Returns an error if the plugin is not found or doesn't implement T.
The plugin-side mirror of GetAs (the battery variant): use it to reach a plugin's concrete type or an optional interface it satisfies without hand-writing the lookup-and-assert boilerplate.
logp, err := framework.PluginGetAs[*logplugin.Plugin](app.Plugins, "log")
func TxFromContext ¶
TxFromContext returns the active *sql.Tx from context when a CRUD handler has wrapped the operation in a transaction. Re-exports framework/db.TxFromContext for callers (typed hooks etc.) that import framework directly.
func ValidateProcessModuleDescriptor ¶ added in v0.31.0
func ValidateProcessModuleDescriptor(d ProcessModuleDescriptor, approved ApprovedGrants) ([]access.Permission, error)
ValidateProcessModuleDescriptor is the pure install-time validation for a process module descriptor (design §3 decision B + §5). It checks:
- name/version present and well-formed;
- artifact path present; ArtifactSHA256 + SurfaceSHA256 are non-empty hex SHA-256;
- route IDs unique and well-formed; methods upper-case HTTP verbs; paths begin with "/";
- tool IDs unique and digests are hex SHA-256;
- every requested grant passes access.ValidScope and the non-grantable carve-out (CrossOwnerRead + broad wildcards); ≤32 grants;
- effective grants = requested ∩ approved, computed and returned;
- limits do not RAISE any host default (deadline ≤ 10s, frame bytes ≤ scanner cap, inflight ≤ host default);
- migration group, if set, matches [moduleIdentPattern].
It returns the computed effective-grant set on success. The descriptor is NOT mutated.
func WithDBContext ¶ added in v0.3.2
WithDBContext returns a derived context carrying db. The App stamps this onto every request context (see App.DBContextMiddleware) so a screen's RenderCtx(ctx) / Load(ctx) can reach the same *sql.DB the framework holds without a package-level global handle.
Types ¶
type ACPConfig ¶ added in v0.10.0
type ACPConfig struct {
ProtocolVersion string // protocol.version (protocol.name is fixed "acp")
APIBaseURL string // api_base_url
Transports []string // supported transports
CapabilitiesServices []map[string]any // capabilities.services
}
ACPConfig configures /.well-known/acp.json (agenticcommerce.dev).
type AccessControl ¶
type AccessControl = entity.AccessControl
type AccessDeclaration ¶ added in v0.5.0
type AccessDeclaration = entity.AccessDeclaration
type AgentAuthBlock ¶ added in v0.10.0
type AgentAuthBlock struct {
// Skill is the URL of the /auth.md manifest. Defaults to <base>/auth.md.
Skill string
// IdentityEndpoint registers/looks up an agent identity.
IdentityEndpoint string
// ClaimEndpoint claims a pending identity.
ClaimEndpoint string
// EventsEndpoint receives async notifications (e.g. revocation).
EventsEndpoint string
// IdentityTypesSupported defaults to ["anonymous","identity_assertion","service_auth"].
IdentityTypesSupported []string
}
AgentAuthBlock is the agent_auth object advertised in the OAuth authorization-server metadata (WorkOS agentic-registration profile).
type AgentSkillEntry ¶ added in v0.10.0
type AgentSkillEntry struct {
Name string `json:"name"`
Type string `json:"type"` // "skill-md" (default) or "archive"
Description string `json:"description"`
URL string `json:"url"`
Digest string `json:"digest"` // "sha256:<hex>" of the artifact at URL
}
AgentSkillEntry is one skill in the /.well-known/agent-skills/index.json. Mirrors the v0.2.0 discovery schema.
type App ¶
type App struct {
Registry *Registry
MCP *mcp.Server
DB *sql.DB
Config AppConfig
Plugins *PluginManager
Storage upload.Storage // optional; enables multipart on Image/File fields
Batteries *BatteryManager
// contains filtered or unexported fields
}
App is the top-level application container. It wires together the entity registry, router, MCP server, and database.
func NewApp ¶
NewApp creates a new App with the given options. It initializes default Registry, Router, and MCP Server if not provided.
func NewUIHostApp ¶
NewUIHostApp builds an App and mounts the given host on it in one call — the near-universal shape for SSR/UIHost apps, which otherwise repeat
app := framework.NewApp(opts...) app.Mount(host)
host is any Mountable (typically a *uihost.Host). Returns the App for fluent chaining.
func (*App) AddCron ¶
AddCron registers a Scheduler with the app's lifecycle: it starts when Start runs and stops when Stop runs. Returns the App for chaining so users can wire several schedulers in one expression.
The stop side drains through StopContext so in-flight job goroutines are joined before shutdown proceeds — bounded by the drain deadline, so a job that ignores its (already-cancelled) context can't hang SIGTERM forever.
Worker-scoped: under RoleServe this is a no-op — neither the start hook nor the drainer is registered, so a serve-only shutdown never waits on a scheduler that was never started.
func (*App) CrudHandler ¶
func (a *App) CrudHandler(name string) (*crud.CrudHandler, error)
CrudHandler returns a fully-wired in-process CRUD handler for a registered entity — the same handler shape the HTTP routes use (hooks, events, storage, JSON casing, registry). Use it to call CreateOne/UpdateOne/DeleteOne/ListAll directly, e.g. to compose several writes inside App.InTx (pass the InTx ctx so they join the same transaction). Returns an error if no entity is registered under name or the app has no DB.
func (*App) DBContextMiddleware ¶ added in v0.3.2
func (a *App) DBContextMiddleware() router.Middleware
DBContextMiddleware returns middleware that stamps the App's *sql.DB onto every request context so downstream handlers and screens can retrieve it via DBFromContext. When the App has no DB, the returned middleware is a pass-through.
The App installs this automatically as part of the default middleware chain when a DB is configured, so most apps never call this directly. It's exposed for apps that opt out of the default chain (WithoutDefaultMiddleware) and want to wire DB-in-context into their own chain.
func (*App) Entity ¶
func (a *App) Entity(name string, config entity.EntityConfig) *App
Entity registers an entity with the given name and configuration. Returns the App for fluent chaining. Panics on any misconfiguration — convenient for static, hand-written declarations where a bad config is a programming error you want to fail fast on. For generated or untrusted configs (e.g. an AI-authored field, a dynamic schema) where one bad entity should not crash the process, use TryEntity, which returns the error.
func (*App) ExportData ¶ added in v0.20.0
ExportData dumps every entity's rows plus every registered battery table to a portable archive under dir: one <name>.ndjson per source plus a manifest.json. Reads are raw (all physical columns, all rows including soft-deleted), paged by primary-key keyset. See the package comment for the fidelity and SQL-safety contracts.
func (*App) ExportStatic ¶ added in v0.8.0
ExportStatic renders the app to a folder of static HTML + assets using the SSG Builder (framework/static). It locates the mounted UIHost and renders every declared route to a directory-style file, plus all /__gofastr assets the runtime needs to boot — runtime.js, the split runtime modules (themeswitch, shortcut, copy, widgets, …), app.css, and per-component CSS — with query-free filenames so the output serves correctly from any static host (GitHub Pages, S3, …).
basePath is the URL subpath the site is served under (e.g. "/gofastr" for a GitHub Pages project site at https://user.github.io/gofastr/); pass "" for an apex deploy. When set, the builder prefixes every root-absolute asset/nav URL in the HTML and bakes the prefix into runtime.js so dynamically-loaded modules resolve under the mount path.
This is the native replacement for the wget mirror the Pages deploy used to ship: declaration-driven (no crawling), and it dumps the split modules the crawl baked a "?v=" into and 404'd.
Returns an error if no uihost.UIHost is mounted.
func (*App) Flags ¶
func (a *App) Flags() *featureflag.Evaluator
Flags returns the app's feature-flag evaluator, creating one on first call. The default backing store is in-memory; for clustered deployments call SetFlagStore with a Redis- or DB-backed implementation BEFORE any caller invokes Flags — once the lazy default fires, subsequent SetFlagStore calls panic to avoid the silent race where some goroutines still hold a reference to the previous evaluator.
The evaluator is also installed as featureflag.Default() so package- level featureflag.Bool(ctx, "...") calls work from anywhere in the app.
func (*App) Group ¶
func (a *App) Group(prefix string, opts ...routegroup.GroupOption) *routegroup.RouteGroup
Group creates a route group with the given prefix and optional configuration. The group supports its own middleware stack, access policy, OpenAPI tags, and MCP namespacing. Nested groups compose prefixes and middleware.
api := app.Group("/api")
api.Use(authMiddleware)
api.Get("/health", healthHandler)
admin := app.Group("/admin", routegroup.WithAccess(access.RequirePermission("admin:access")))
admin.Entity("settings", settingsConfig)
func (*App) GroupEntity ¶
func (a *App) GroupEntity(g *routegroup.RouteGroup, name string, config entity.EntityConfig) *App
GroupEntity registers an entity with the given configuration inside a RouteGroup. CRUD routes mount at <group-prefix>/<entity-table>, MCP tools are namespaced under the group's MCPNamespace, and the OpenAPI tag reflects the group's OpenAPITag if set.
This is the group-scoped equivalent of App.Entity.
func (*App) HookRegistry ¶
func (a *App) HookRegistry(entityName string) *hook.HookRegistry
HookRegistry returns (or creates) the hook registry for a named entity.
func (*App) ImportData ¶ added in v0.20.0
ImportData restores an archive written by ExportData into the live database. It validates the WHOLE archive before writing a single row (missing manifest, unknown source, incompatible columns, checksum mismatch are all rejected up front), then writes every source inside a single transaction — rolling back on any error. Original ids/timestamps/owner/tenant and soft-deleted rows are preserved verbatim.
func (*App) InTx ¶
InTx runs fn inside a database transaction opened on the App's DB. The inner context carries the *sql.Tx so any code path that calls TxFromContext (typed hooks, the various do* helpers, generated repo methods invoked via WithTx) participates atomically.
Convenience wrapper for callers that aren't already inside a CRUD hook — e.g. seeders, batch jobs, multi-entity write paths that need an explicit boundary. If fn returns an error, the tx rolls back and that error is returned unchanged.
func (*App) InitPlugins ¶
InitPlugins initializes all registered plugins and batteries by calling their Init(app) method. Plugins go first (registration order), then batteries (dependency-resolved order). Each module does everything it needs from inside Init — register routes, add middleware, register MCP tools, attach hooks, swap the logger, etc.
Idempotent: the first successful call latches an internal flag so any later call returns nil without re-running plugin Inits. This lets tests call InitPlugins() manually pre-Start without colliding with the implicit call inside Start.
func (*App) IsEnabled ¶
IsEnabled is a convenience wrapper that calls Flags().Bool with the supplied context. Use it from handler code that doesn't otherwise need a direct reference to the evaluator.
The context is expected to already carry an EvalContext (via featureflag.WithContext) when user/tenant gating matters; without one, the call still works but only the kill-switch (Flag.Enabled) and uniform rollout are consulted.
func (*App) JSONCasing ¶
JSONCasing returns the configured JSON casing strategy. Defaults to CaseCamel if not explicitly set.
func (*App) Lifecycle ¶
Lifecycle returns the App's graceful-shutdown coordinator. Batteries and plugins use Lifecycle().RegisterDrainer / RegisterHealthChecker to participate in Shutdown beyond the simple OnStop hook.
func (*App) Logger ¶
Logger returns the App-local *slog.Logger. Middleware and plugins should call this — not slog.Default() — so that a logging plugin can replace the destination without rewiring globals.
Always non-nil. NewApp seeds the App with a JSON-to-stderr logger that is independent of slog.Default; an unrelated slog.SetDefault elsewhere in the process does not redirect this App's logs.
func (*App) Modules ¶ added in v0.17.0
func (a *App) Modules() *ModuleManager
Modules returns the app's module manager, through which callers can list modules, query enabled state, and toggle modules at runtime.
func (*App) Mount ¶
Mount attaches a Mountable and registers its routes on the app's router immediately. The default middleware chain is already in place (committed during NewApp), so any handler the Mountable registers is wrapped with it. Returns the app for fluent chaining.
Mountables typically register a NotFound catch-all (e.g. a UI host that renders pages for any unrouted path), so call Mount AFTER any explicit routes you want to take precedence (entity CRUD, custom endpoints).
IMPORTANT — ordering with plugins/batteries: plugin.Init runs at App.Start (or InitPlugins), AFTER Mount has already registered the Mountable's routes. If a plugin's Init registers a more-specific route that overlaps with a Mountable's NotFound catch-all, the plugin's route still wins (ServeMux dispatches by specificity, not by registration order). But if a plugin registers a Mountable-style catch-all itself, it shadows any user routes added after Mount but before InitPlugins. Mount last unless you know what you're doing.
func (*App) Mountables ¶
Mountables returns the Mountables registered via Mount, in registration order. Batteries use it to discover a mounted UI host (type-asserting to *uihost.Host) so they can register screens on the host's render pipeline rather than spinning up a second host. Returns a copy — callers must not mutate the App's internal slice.
func (*App) MustCrudHandler ¶
func (a *App) MustCrudHandler(name string) *crud.CrudHandler
MustCrudHandler is CrudHandler that panics on error — for app setup where a missing entity is a programming mistake.
func (*App) NewModuleMigrationCoordinator ¶ added in v0.31.0
func (a *App) NewModuleMigrationCoordinator(opts ...CoordinatorOption) (*MigrationCoordinator, error)
NewModuleMigrationCoordinator builds the short-lived migration coordinator for a process module (design §7). The operator (or a CLI / deploy job) calls it, runs MigrationCoordinator.Apply with the digest-verified migration set, and the coordinator provisions the per-module Postgres schema+role, runs Up authenticated as that role, and stamps MigrationsAppliedAt so the supervisor lets the module reach Ready. Returns an error if no process modules are registered.
For the Postgres path, pass WithCoordinatorAdminDSN with the host's URL-form DSN (the same one the app boots against). SQLite is trusted/dev-only — the coordinator rejects an untrusted module's migrations on SQLite (fail-closed, design §7 decision F).
func (*App) OnReady ¶ added in v0.5.0
OnReady registers a function to run once the HTTP listener has bound successfully, just before the server begins accepting connections. The addr passed in is the listener's resolved address (a ":0" request arrives with the real port), so it is safe to print in a startup banner: every earlier phase — auto-migrate, seeds, plugin init, OnStart hooks, and the bind itself — has already succeeded. Hooks run in registration order and must not block.
func (*App) OnStart ¶
OnStart registers a function to run once during App.Start, before the HTTP server begins accepting connections. The context passed in is cancelled when Stop is called, so workers should respect it.
Hooks run in registration order; the first to return a non-nil error aborts Start.
func (*App) OnStop ¶
OnStop registers a function to run during App.Shutdown, after the HTTP server has shut down. Hooks run in reverse registration order — the last thing started is the first thing stopped. Internally the hook is wrapped as a lifecycle.Drainer so app-level cleanup and battery drains share one coordinator.
func (*App) OnStopFirst ¶
OnStopFirst registers an OnStop hook that runs LAST under the reverse-order Stop iteration. Useful for plugins (battery/log especially) that must outlive every other shutdown step: their close hook needs to fire AFTER every other OnStop has had a chance to emit log entries.
Without this, a user that registers app.OnStop BEFORE RegisterPlugin(log) gets the order inverted on reverse iteration — log's close runs first, the user's OnStop logs into closed sinks.
func (*App) Outbox ¶ added in v0.15.0
Outbox returns the transactional event outbox, or nil when the app was built without WithOutbox. Use it to stage your own events inside an App.InTx transaction (Append), inspect delivery state (List), or replay dead rows.
func (*App) ProcessModules ¶ added in v0.31.0
func (a *App) ProcessModules() *ProcessModuleSupervisor
ProcessModules returns the process-module supervisor, or nil when the app registered no process modules. Through it the operator enables / disables / revokes a module at runtime.
func (*App) RegisterBattery ¶
RegisterBattery registers a heavyweight, lifecycle-aware battery module (auth, search, cache, etc.) with the application. deps lists battery names that must be initialized before this one. Returns the App for chaining.
Batteries are initialized in dependency-resolved order during App.Start, before the HTTP server binds. Each battery's Init does whatever it needs by calling into the App (routes, middleware, hooks, MCP tools). Batteries that also implement BatteryLifecycle get their OnStart/OnStop fired by the App at the appropriate moment.
Example:
app.RegisterBattery(auth.New(auth.Config{...}), "search") // depends on search battery
func (*App) RegisterEntities ¶
func (a *App) RegisterEntities(entities map[string]entity.EntityConfig) *App
RegisterEntities registers each (name, config) pair via App.Entity in alphabetical-by-name order. Sorting matters: Entity has order-sensitive side effects — router registration, MCP tool list order, OpenAPI tag emission — and Go's map iteration is randomised, so unsorted iteration would mean non-deterministic /openapi.json bytes across restarts (breaking ETag caching) and non-deterministic MCP tools/list responses. FK relations stay safe because AutoMigrate also topologically sorts.
Returns the App for fluent chaining.
app.RegisterEntities(map[string]entity.EntityConfig{
"foods": foodsConfig,
"meals": mealsConfig,
"users": usersConfig,
})
func (*App) RegisterModule ¶ added in v0.17.0
RegisterModule validates the name, registers the module as a battery (with deps = Manifest().DependsOn so topo-sort orders module init), and records the manifest in the module manager.
app.RegisterModule(myModule) // registers as battery + records manifest
func (*App) RegisterPlugin ¶
RegisterPlugin registers a plugin with the application's plugin manager. Returns the App for fluent chaining.
Panics if InitPlugins has already run — plugins must be registered before App.Start (or the explicit InitPlugins call) so their Init fires. The panic is a clear contract violation rather than a silent no-op that would have the new plugin's routes / middleware vanish.
func (*App) RegisterProcessModule ¶ added in v0.31.0
func (a *App) RegisterProcessModule(desc ProcessModuleDescriptor, approved ApprovedGrants) *App
RegisterProcessModule installs a process-isolated third-party module (issue #37, wave 2a). It validates the descriptor, ensures the ProcessModuleStore schema, registers the module with the supervisor AND the in-process ModuleManager (so the route gate 404s when the module is disabled), mounts each declared route behind the gate, and wires the supervisor as the process-module coordinator for ModuleManager.List introspection.
Routes are attributed to the module name via setCurrent/clearCurrent so the existing route gate (Enabled → 404, indistinguishable from uninstalled) governs them. The Ready layer lives in the proxy handler (enabled-but-not-Ready → 503 + Retry-After). The supervisor's loops are started in [App.runStartHooks]; the drain drainer is registered via lifecycle.PrependDrainer so children drain first during shutdown.
Panics on validation or schema errors (caller-time misconfiguration — the descriptor is operator-supplied at install). Returns the App for chaining.
func (*App) RegisterReadiness ¶
RegisterReadiness adds a readiness check. Names are not required to be unique — duplicates surface in the /readyz response as repeated rows, which is occasionally useful (the same backend probed at two layers).
Pass a timeout on the context if your check could hang; /readyz also applies an overall deadline, but per-check timeouts give better signal in the response.
func (*App) Role ¶ added in v0.16.0
Role returns the resolved process role (all/serve/worker). It is set once in NewApp and never changes afterward, so OnStart hooks and other setup code can gate their own background work on it — e.g. skipping an expensive warm-up that only makes sense when serving HTTP:
app.OnStart(func(ctx context.Context) error {
if app.Role() == framework.RoleWorker {
return nil // the worker process doesn't serve this cache
}
return warmRenderCache(ctx)
})
Plain OnStart hooks are role-agnostic and run in every role; worker- scoped registration (cron/queue/outbox) is gated internally.
func (*App) Router ¶
Router returns the App's *router.Router for advanced use (plugin authors, batteries that need to register routes with custom matching, sub-router construction). Application code should prefer the App-level helpers:
- App.Use(mw) — register middleware (instead of Router().Use)
- App.Get/Post/... — register routes (instead of Router().Handle)
- App.Group(prefix) — sub-routes (instead of Router().Group)
Both forms are functionally equivalent — App.Use forwards to Router().Use — but the App-level surface is the canonical one in docs and examples.
Exposed as a method (rather than a field) so plugins and batteries can swap or wrap the router during Init without callers depending on direct field assignment.
func (*App) Routine ¶
Routine registers a stored routine (function, procedure, trigger, or view) as a first-class migration object. Its Up runs on every boot (idempotent CREATE OR REPLACE) after tables are migrated, and `migrate generate` tracks it for reversible versioned migrations. Returns App for chaining.
func (*App) RoutinesFS ¶ added in v0.30.0
RoutinesFS loads routines authored as embedded SQL files via migrate.RoutinesFS and registers each one as if App.Routine had been called on it. This is the primary authoring path for stored procedures: an app writes `db/routines/compute_totals.pg.sql` containing `CREATE OR REPLACE FUNCTION …` and calls `app.RoutinesFS(embeddedFS, "db/routines")`. See migrate.RoutinesFS for the filename grammar (`<name>.sql`, `<name>.down.sql`, `<name>.pg.sql`, `<name>.sqlite.sql`) and the loud-rejection rules (empty file, empty dir, plain+dialect Up collision). A loader error panics at registration time — mirroring App.View's misconfig panic — so the exact embed-path/file error surfaces in the console instead of shipping a half-loaded routine set.
func (*App) RunReadinessChecks ¶ added in v0.17.0
func (a *App) RunReadinessChecks(ctx context.Context) ReadinessResponse
RunReadinessChecks executes all registered readiness checks in parallel and returns the structured response. Exported so batteries (e.g. battery/setup's HealthStep) can run the same checks /readyz does without going through HTTP.
func (*App) RunWithSignals ¶
RunWithSignals blocks until SIGINT or SIGTERM is received, then runs Shutdown. Returns Shutdown's error, or nil if ctx is cancelled before a signal arrives.
func (*App) SetFlagStore ¶
func (a *App) SetFlagStore(s featureflag.Store) *App
SetFlagStore swaps the underlying store. Must be called before any caller has triggered the lazy default; a second SetFlagStore call (or any call after Flags() / IsEnabled has already been used) panics to avoid the silent race where stale references to the previous evaluator persist in handler closures.
Useful for tests that want a preconfigured store, or for production wiring that uses a persistent backend — wire it during NewApp setup, before any request runs.
func (*App) SetLogger ¶
SetLogger replaces the App's logger. Atomic; safe to call concurrently with in-flight requests — atomic.Pointer.Store is race-free, and middleware reading via App.Logger() sees the new value on the next request.
Panics if l is nil — the App's logger is always non-nil; pass a discard logger (slog.New(slog.DiscardHandler)) to silence output.
func (*App) Shutdown ¶
Shutdown gracefully stops the HTTP server, stops every registered battery in reverse dependency order, then runs each OnStop hook in reverse registration order. Matches net/http.Server.Shutdown's signature (takes a deadline ctx) but does the FULL lifecycle teardown. Safe to call multiple times — subsequent calls are no-ops.
Call this from your signal handler.
func (*App) T ¶
T is a convenience wrapper around the app's Translator. Returns the bare key when no Translator has been wired via WithI18n.
The ctx is expected to already carry a Locale (the i18n middleware attaches one per request from Accept-Language); when it doesn't, the Translator's fallback locale is used.
func (*App) Table ¶
Table registers a raw, non-entity table for migration only — no CRUD, no HTTP routes, no validation, no auto-injected columns. The table participates in auto-migrate, diffing, and generation alongside entities (including foreign keys that cross between the two). For users who want migration coverage of a table without the entity machinery. Returns App for chaining.
func (*App) Translator ¶
func (a *App) Translator() *i18n.Translator
Translator returns the wired Translator, or nil if WithI18n was never called. Useful for handlers that need to drive locale-aware formatting beyond simple lookups.
func (*App) TryEntity ¶
func (a *App) TryEntity(name string, config entity.EntityConfig) (err error)
TryEntity is the error-returning variant of Entity: it registers an entity and returns an error on any misconfiguration instead of panicking. It also recovers panics from deeper validation (e.g. an invalid TenantField) and converts them to errors, so a single bad config can never take down the process — the property an agent-driven authoring loop needs.
func (*App) Use ¶
func (a *App) Use(mw ...router.Middleware) *App
Use appends middleware to the app's router chain. The default chain (installed by NewApp unless WithoutDefaultMiddleware is set) stays in place — Use adds to it, never silently replaces it. Plugins call Use from their Init to contribute middleware; router late-binding means these additions also wrap routes registered before the plugin loaded.
func (*App) View ¶
View registers a database view — a virtual table built from other entities. The view is created on boot after its source tables (and tracked reversibly by `migrate generate`), and, when it declares Columns, it is also exposed through the ORM as a READ-ONLY entity: List/Get and the query layer work, but no write routes are registered. Returns App for chaining.
func (*App) WithAuditLog ¶
func (a *App) WithAuditLog(cfg AuditConfig) *App
WithAuditLog enables audit logging on every entity registered on the app (or the subset named in cfg.Entities). Call AFTER Entity registrations.
Returns the app for fluent chaining. Panics if the audit table cannot be created — this is initialization-time work so loud failure is preferable to silent log loss.
func (*App) WithSeed ¶ added in v0.3.2
WithSeed registers a seed function to run during App.Start AFTER auto-migration completes (so every entity table exists) and BEFORE the HTTP server accepts traffic. This is the documented fix for the common "no such table" footgun: calling db.Exec("INSERT …") from main() before Start() runs migrations fails, because the table isn't there yet.
Seed funcs run in registration order. The first to return a non-nil error aborts Start (the partial-startup teardown drains anything an earlier phase spawned). The context is the app's lifecycle context, so a long-running seed respects shutdown.
WithSeed composes with the per-entity EntityConfig.Seed callback: entity seeds (idempotent, ledger-tracked via _gofastr_seeded) run first as part of auto-migration's RunSeeds phase; WithSeed funcs run immediately after, in the order registered. Use WithSeed for cross-entity or app-level seed logic that doesn't belong to a single entity.
site := framework.NewApp(framework.WithDB(db))
site.Entity("foods", foodsConfig)
site.WithSeed(seedFoods) // runs after the foods table is migrated
Returns the App for fluent chaining.
type AppConfig ¶
type AppConfig struct {
Name string // application name
JSONCase crud.JSONCase // JSON key casing: "camelCase" (default) or "snake_case"
DebugEndpoints bool // opt-in for /.debug/* endpoints
NoLLMMD bool // disable auto-generated /llm.md entity docs
// PublicOpenAPI serves /openapi.json without the auth gate. By default
// the spec is auth-gated (it enumerates every route), so a minimal app
// returns 401 there — which surprised users following the quickstart
// curl. Set true (or use WithPublicOpenAPI) when the spec is meant to be
// public, e.g. a docs site or an internal API behind a network boundary.
// The Swagger UI at /api/docs/ is always reachable; this only governs
// the raw spec JSON.
PublicOpenAPI bool
// APIPrefix mounts every auto-CRUD entity route (list/get/create/update/
// delete + _batch + _events + per-entity llm.md) under this path — e.g.
// "/api" serves GET /api/posts instead of GET /posts. Empty (default)
// keeps the bare entity-name mounts, so this is not a breaking change.
// The generated OpenAPI spec expresses the prefix via its server URL.
// GroupEntity routes are unaffected — a group owns its own prefix. MCP
// tool names are unchanged.
APIPrefix string
// RequestTimeout caps the per-request wall-clock budget enforced by
// the default middleware chain. Zero (default) installs a 30s cap.
// Set a positive duration to override. To disable the timeout
// middleware entirely, set DisableRequestTimeout — overloading
// sign for "disable" is too easy to trip on (e.g. accidentally
// subtracting two timestamps).
RequestTimeout time.Duration
// DisableRequestTimeout removes the Timeout middleware from the
// default chain entirely. Useful for long-running uploads / SSE;
// pair with per-handler ctx deadlines if you still need bounded
// request lifetime.
DisableRequestTimeout bool
// SecurityHeaders configures the defensive HTTP response headers
// (Content-Security-Policy, Referrer-Policy, X-Frame-Options,
// Permissions-Policy, CORP, COOP, HSTS) emitted by the SecurityHeaders
// middleware in the default chain. The zero value installs the
// framework's strict defaults (default-src 'self', no 'unsafe-inline');
// set fields to override — e.g. ContentSecurityPolicy to allow
// style-src 'unsafe-inline' for a third-party CSS dependency. Unset
// fields keep their built-in defaults, so a partial override never
// silently drops a defensive header. See [WithSecurityHeaders].
SecurityHeaders middleware.SecurityHeadersConfig
// ShutdownTimeout bounds the graceful drain that the default
// SIGINT/SIGTERM handler runs via App.Shutdown. Zero installs the
// 15s default. The drain stops accepting connections, waits for
// in-flight requests, force-closes whatever remains at the deadline
// (an open SSE stream never goes idle), then stops batteries and
// runs OnStop hooks.
ShutdownTimeout time.Duration
// DisableSignalHandling opts out of the SIGINT/SIGTERM handler that
// Start installs by default. Set it when the embedding process owns
// signal handling and calls App.Shutdown (or RunWithSignals) itself.
DisableSignalHandling bool
}
AppConfig holds application-level configuration.
type AppOption ¶
type AppOption func(*App)
AppOption is a functional option for configuring an App.
func WithACP ¶ added in v0.10.0
WithACP serves /.well-known/acp.json with the site's ACP discovery metadata.
func WithAPIPrefix ¶
WithAPIPrefix mounts auto-CRUD entity routes under prefix (e.g. "/api"). See AppConfig.APIPrefix. A leading slash is added and a trailing slash trimmed, so "api", "/api", and "/api/" all behave identically.
func WithAgentSkills ¶ added in v0.10.0
func WithAgentSkills(skills []AgentSkillEntry) AppOption
WithAgentSkills serves /.well-known/agent-skills/index.json enumerating the host's published Agent Skills (per the agent-skills-discovery-rfc). The host provides the entries (name/type/url/digest of each SKILL.md or archive it publishes); an empty list still satisfies the discovery check.
func WithAuthMD ¶ added in v0.10.0
func WithAuthMD(cfg AuthMDConfig) AppOption
WithAuthMD serves /auth.md and (when AgentAuth is set) merges an agent_auth block into the OAuth authorization-server metadata. Pair with WithOAuthAuthorizationServer for the agent_auth block to be emitted.
func WithConfig ¶
WithConfig sets the application config. It merges into whatever the granular options (WithAPIPrefix, WithPublicOpenAPI, WithName, …) have already set rather than replacing the struct wholesale, so option order doesn't silently discard config: every field WithConfig sets to a non-zero value wins; a zero field preserves the existing value. To turn a boolean back off, use the granular setter after WithConfig instead of relying on a zero-valued field.
TestWithConfigCoversEveryField pins the field list — extend this merge when adding an AppConfig field.
func WithFanout ¶ added in v0.16.0
WithFanout attaches a cross-replica fanout (core/fanout.Fanout) to the app's real-time lane. The event bus is bridged — every locally-emitted event is mirrored to the other replicas and re-emitted on their buses, so entity `_events` SSE streams work regardless of which replica holds the connection — and any Mountable that supports SetFanout (a mounted UI host) gets its island manager wired the same way.
SEMANTICS: with a fanout attached the bus becomes a broadcast — every On/Subscribe handler fires on EVERY replica. That is correct for UI push and wrong for side effects; per-event work belongs on the durable lane (WithOutboxConsumer). Handlers that derive new events must gate on event.IsRemote(ctx). See the events doc, "Cross-replica fan-out".
The fanout is caller-owned: construct it before NewApp (e.g. framework/fanout.NewPostgres) and close it after Shutdown. The bridge itself is detached by Shutdown. Panics if f is nil.
func WithFileStorage ¶
WithFileStorage sets the default upload.Storage used by CRUD handlers to persist files for Image and File entity fields when a multipart request arrives. Without this option, multipart requests on those fields fail with a clear error.
func WithGrantStore ¶ added in v0.36.0
func WithGrantStore(gs *access.GrantStore) AppOption
WithGrantStore registers the app's RBAC GrantStore so the framework can auto-wire cross-replica grant/revoke invalidation. When a fanout is also attached (WithFanout), every GrantStore.Grant/Revoke publishes a refresh-signal on the "gofastr.access" lane and each replica re-reads the named role's grants from the DB into its local RolePolicy — never trusting the message body. Without a fanout the store is usable but grant/revoke stays local to this process (other replicas heal on restart). The store MUST already be constructed (NewGrantStore) and have EnsureSchema + LoadInto run on it before Start; the framework only wires the fanout subscription.
func WithI18n ¶
func WithI18n(tr *i18n.Translator) AppOption
WithI18n installs a Translator and wires its locale-negotiation middleware into the default chain. Handlers downstream can call App.T(ctx, key, ...) for translated strings driven by the caller's Accept-Language. Also installed as i18n.Default() so the package- level i18n.T helper works from anywhere.
Panics when paired with WithoutDefaultMiddleware — register the middleware explicitly in your custom chain in that case.
func WithIdempotency ¶
func WithIdempotency(cfg middleware.IdempotencyConfig) AppOption
WithIdempotency adds an Idempotency-Key middleware to the default chain. Pass middleware.IdempotencyConfig{} to take all defaults; the option is otherwise idiomatic-Go composition over the existing middleware.Idempotency primitive.
Has no effect when WithoutDefaultMiddleware is also set — wire your own chain explicitly in that case.
func WithLocaleResolver ¶ added in v0.19.0
WithLocaleResolver installs a custom locale resolver consulted BEFORE the X-Locale / Accept-Language headers during locale negotiation. Use it to make a stored per-user locale (e.g. a cookie set by a "change language" handler) win over the browser's Accept-Language.
Pair with i18n.CookieLocale for the common cookie case:
a := framework.NewApp(
framework.WithI18n(tr),
framework.WithLocaleResolver(i18n.CookieLocale("locale")),
)
Panics if used without WithI18n — locale resolution is meaningless without a translator/catalog to resolve against.
func WithLogger ¶
WithLogger sets the App's *slog.Logger. Same effect as calling App.SetLogger after NewApp; available as an option for symmetry. Panics if l is nil — the App's logger is always non-nil; pass a discard logger (slog.New(slog.DiscardHandler)) if you want to silence output.
func WithMCP ¶ added in v0.10.0
func WithMCP() AppOption
WithMCP exposes the app's MCP server at /mcp using the Streamable HTTP transport (POST JSON-RPC + GET Server-Sent Events), so a host doesn't have to hand-wire fwApp.Router().Handle("POST", "/mcp", fwApp.MCP). This is the agent-ready default: combined with uihost.WithAgentReady (which advertises /mcp via the agent card + Link headers) it makes the server's tools discoverable to MCP clients. Calling this AND manually mounting /mcp will panic with a route conflict — pick one.
func WithMCPApp ¶ added in v0.33.0
WithMCPApp registers an MCP App on the app's /mcp server: an interactive HTML widget (served as a `ui://` resource) plus the tool that launches it, wired together in one call. The tool's `_meta` gets the standard `ui.resourceUri` linkage (and the ChatGPT Apps SDK compat alias) pointing at the resource, so a spec-compliant host (Claude, ChatGPT) renders the widget inline when the model calls the tool.
This mirrors how WithMCPControl / WithMCPIntrospection bundle an MCP surface: the registration is queued and applied during InitPlugins, after the rest of the tool surface is in place. It is an explicit opt-in, so a duplicate tool name or resource uri is a hard build error.
The widget HTML is the app author's job — a single self-contained file with inline JS/CSS works and needs no build step (a //go:embed string covers most widgets). Attach csp/permissions via the AppConfig's CSP / Permissions fields; they ride on the resource's `_meta.ui`. AppConfig.HTML is a static string; for dynamic or per-caller widget HTML, drop to the primitives (app.MCP.RegisterResource + RegisterTool with mcp.WithToolMeta) directly.
Requires the /mcp server to be mounted (WithMCP, or the dev-loop auto-mount).
func WithMCPControl ¶ added in v0.25.0
func WithMCPControl() AppOption
WithMCPControl installs the MUTATING MCP tools that let a connected agent control the running App. Separate from WithMCPIntrospection — which stays strictly read-only — so each surface opts into exactly the trust level its /mcp endpoint warrants:
- Introspection (read-only) is safe wherever disclosure is: docs sites, staging, local dev.
- Control (mutating) belongs on /mcp endpoints reachable only by trusted callers — the local dev loop, an authenticated tunnel. Blueprint-generated apps wire it gated on the dev env for this reason.
Tools registered:
- app_module_enable: enable a registered module at runtime. The state persists through the module store and respects dependency ordering (enabling a module whose dependency is disabled fails).
- app_module_disable: disable a registered module at runtime. Fails closed if enabled modules depend on it — no cascades.
Code-level changes (screens, entities, handlers) are NOT an MCP concern: that is the `gofastr dev` edit-rebuild-reload loop. MCP control mutates runtime STATE the app already models.
func WithMCPIntrospection ¶
func WithMCPIntrospection() AppOption
WithMCPIntrospection installs a set of MCP tools that expose the running App's structure to an agent: registered routes, plugins, batteries, app config, readiness status. Opt-in because:
- The tools are read-only but cumulatively reveal a lot about the app's shape (every mounted route, every loaded module, every config field). Production apps that expose MCP to untrusted callers should weigh the disclosure surface before enabling.
- The framework should stay zero-config-friendly: nothing should show up on the MCP server unless the operator asked for it.
Tools registered:
- app_routes: list every (method, pattern) registered on the router.
- app_plugins: list registered plugins (name only).
- app_batteries: list registered batteries with deps + lifecycle status.
- app_config: return the AppConfig snapshot (Name, JSONCase, timeouts…).
- app_readiness: run every registered readiness check and report results.
- framework_docs_list / framework_docs_get / framework_docs_search: expose the framework's markdown docs (embedded at build time, so they match the framework version this binary was built against — no GitHub fetch).
func WithMCPServer ¶
WithMCPServer sets a custom MCP server.
func WithMetrics ¶
func WithMetrics() AppOption
WithMetrics enables HTTP request metrics (per-route counts, status classes, latency histograms) in the default middleware chain and mounts a Prometheus-format /metrics endpoint. The endpoint is unauthenticated by design (scrape it from inside your network / behind your ingress). Panics when paired with WithoutDefaultMiddleware — mount middleware.MetricsMiddleware and middleware.MetricsHandler yourself in that case.
func WithOAuthAuthorizationServer ¶ added in v0.10.0
func WithOAuthAuthorizationServer(cfg OAuthAuthorizationServerConfig) AppOption
WithOAuthAuthorizationServer serves /.well-known/oauth-authorization-server (RFC 8414). Use it when the app is an OAuth2/OpenID issuer so clients can discover endpoints + supported capabilities.
func WithOAuthProtectedResource ¶ added in v0.10.0
func WithOAuthProtectedResource(cfg OAuthProtectedResourceConfig) AppOption
WithOAuthProtectedResource serves /.well-known/oauth-protected-resource (RFC 9728) from cfg. Use it when the app exposes OAuth-token-protected resources so clients can discover how to obtain and present tokens.
func WithOutbox ¶ added in v0.15.0
WithOutbox enables the transactional event outbox (framework/outbox): CRUD lifecycle events are written to an outbox table INSIDE the same transaction as the entity write, and a relay goroutine (started by App.Start, drained on Shutdown) delivers each committed row to the declared durable consumers. This closes the crash window where a plain post-commit emit is lost, and makes delivery at-least-once per consumer — consumers that care must dedupe on Event.ID. Requires WithDB; NewApp panics otherwise.
The relay delivers ONLY to consumers declared via WithOutboxConsumer; it no longer publishes to the live event bus. The real-time lane (SSE EventStream, ephemeral On/Subscribe) is fed independently by EmitEvent, so the two lanes never duplicate. opts are forwarded to outbox.New (outbox.WithTable, outbox.WithPollInterval, …).
func WithOutboxConsumer ¶ added in v0.16.0
func WithOutboxConsumer(name, eventType string, handler event.EventHandler) AppOption
WithOutboxConsumer declares a durable outbox consumer. Requires WithOutbox (NewApp panics if the outbox isn't also enabled). name is a stable identity used to track per-consumer delivery across restarts/replicas; (eventType, name) must be unique. handler is invoked once per delivery with Event.ID set to the outbox row id (dedup key) — it must be idempotent (at-least-once delivery). A handler that errors or panics is retried with backoff and eventually dead-lettered independently of its sibling consumers (sibling isolation).
func WithPublicOpenAPI ¶
func WithPublicOpenAPI() AppOption
WithPublicOpenAPI serves /openapi.json without the auth gate. Equivalent to setting AppConfig.PublicOpenAPI. Use when the spec is meant to be public (docs sites, internal APIs behind a network boundary).
func WithReadinessTimeout ¶
WithReadinessTimeout overrides the per-request /readyz deadline. Default 5 seconds. Checks that don't return within the deadline are reported as errors with status "timeout".
func WithRole ¶ added in v0.16.0
WithRole sets the process role explicitly, overriding the GOFASTR_ROLE env var. Panics in NewApp if r is not one of RoleAll / RoleServe / RoleWorker — matching how every other invalid option fails fast at construction rather than silently degrading.
func WithSecret ¶ added in v0.38.0
WithSecret sets the app-wide secret. Subsystem keys (starting with the uihost session-signing key) are HKDF-derived from it, so one secret shared across replicas is all a multi-replica deployment configures. Equivalent zero-code path: set GOFASTR_SECRET in the environment (or a .env file); an explicit WithSecret wins over the env var. Panics on a secret shorter than 32 characters — a short secret weakens every derived key at once.
func WithSecurityHeaders ¶ added in v0.29.0
func WithSecurityHeaders(cfg middleware.SecurityHeadersConfig) AppOption
WithSecurityHeaders configures the defensive HTTP response headers (Content-Security-Policy, Referrer-Policy, X-Frame-Options, …) emitted by the SecurityHeaders middleware in the default chain. Equivalent to setting AppConfig.SecurityHeaders. The zero value keeps the framework's strict defaults; override individual fields (e.g. ContentSecurityPolicy) to relax a specific directive — unset fields retain their built-in defaults so a partial override never drops a defensive header. See middleware.SecurityHeadersConfig.
This removes the need to shadow the default middleware with a hand-rolled SecurityHeaders middleware just to change one directive.
func WithSetup ¶ added in v0.17.0
func WithSetup(r SetupRunner) AppOption
WithSetup wires a first-run setup runner. When the runner reports incomplete setup at boot, Start either runs the steps headlessly (env provides every required value) or serves an interactive wizard until setup finishes — then atomically swaps to the real app router.
Overrides via the GOFASTR_SETUP env var:
- "off": never enter setup mode (Start proceeds normally)
- "force": enter setup mode even if Complete reports done (rescue)
- invalid: Start fails loudly
func WithTracing ¶
func WithTracing() AppOption
WithTracing enables the OpenTelemetry tracing middleware in the default chain. Each request runs in a span with method/route/status attributes. Spans no-op until you install a TracerProvider via otel.SetTracerProvider (e.g. an OTLP exporter), so this is safe to leave on. Panics when paired with WithoutDefaultMiddleware.
func WithUCP ¶ added in v0.10.0
WithUCP serves /.well-known/ucp with the site's UCP discovery metadata.
func WithVerboseReadiness ¶
func WithVerboseReadiness() AppOption
WithVerboseReadiness opts in to including each check's error.Error() string in the /readyz JSON response. Default is to omit error text because /readyz is typically reachable without authentication and raw error strings frequently leak internal IPs, connection strings, or other infrastructure detail.
Turn this on only when the probes are scoped to a trusted listener or behind auth.
func WithWebBotAuth ¶ added in v0.10.0
func WithWebBotAuth(cfg WebBotAuthConfig) AppOption
WithWebBotAuth serves /.well-known/http-message-signatures-directory with the site's signing JWKS.
func WithoutAutoMigrate ¶ added in v0.15.0
func WithoutAutoMigrate() AppOption
WithoutAutoMigrate disables the entity auto-migration that App.Start otherwise runs before serving. Use it in deployments whose policy forbids unattended schema changes on boot: generate the entity DDL into versioned migration files instead (`gofastr migrate generate <name>`) and apply them as an explicit step (`gofastr migrate up`). Entity seeds still run at Start — they are idempotent data, not schema — which also means an entity WITH seeds fails Start fast when its table is missing, instead of the app serving against an unmigrated schema.
func WithoutDefaultMiddleware ¶
func WithoutDefaultMiddleware() AppOption
WithoutDefaultMiddleware disables the default middleware chain (recovery, request-id, logging, security headers, timeout). Use this when you want full control over middleware composition via Use().
type ApplyOptions ¶
type ApplyOptions = migrate.ApplyOptions
type ApprovedGrants ¶ added in v0.31.0
type ApprovedGrants []access.Permission
ApprovedGrants is the operator-approved subset of a descriptor's RequestedGrants. It is a parameter to ValidateProcessModuleDescriptor, not a UI — this wave treats approval as an explicit set the caller supplies (design §5: "approval is a parameter, not a UI").
type ApprovedMigration ¶ added in v0.31.0
ApprovedMigration is one operator-approved migration loaded from the digest-verified module artifact. SHA256 is the approved digest of Up (hex); the coordinator computes the same digest and rejects a mismatch (a child cannot substitute SQL). An empty SHA256 skips the per-migration digest check (the artifact-level digest is still the trust anchor); the install tool SHOULD always set it.
type AuditConfig ¶
type AuditConfig struct {
// Table is the destination table for audit rows. Defaults to "audit_log".
Table string
// Actor resolves the actor id (typically a user id) from the request
// context. Return "" when no actor is attached (e.g. system writes).
Actor func(context.Context) string
// Entities restricts auditing to the named entities. Empty means audit
// every registered entity.
Entities []string
// Redact, when non-nil, is called with the entity name and a
// defensive copy of the row about to be serialised into the
// `diff` column. Return either the modified input (safe to mutate
// — the framework already copied it) or a fresh map. Either works.
//
// When nil, the framework applies a default sensitive-field scrub
// (see defaultSensitiveSuffixes) — fields whose names look like
// passwords, tokens, secrets, or keys are dropped from the diff so
// a host that forgot to configure Redact doesn't accidentally
// stream credentials into the audit log.
//
// Redact is invoked for AfterCreate, AfterUpdate, AND AfterDelete.
// On delete the input is `map[string]any{"id": <record_id>}`; the
// returned map's "id" value (if any) becomes the audit row's
// record_id, letting hosts pseudonymise the natural key on delete
// too. If the callback returns a map with no `id` key the framework
// falls back to the original — silently erasing the record_id is
// an audit-forensics erasure primitive we don't want to expose.
//
// Returning nil is equivalent to returning an empty map. A panic
// inside Redact is recovered: the audit row is still written using
// the original pre-redact value so audit coverage isn't lost to a
// misbehaving callback.
Redact func(entity string, row map[string]any) map[string]any
}
AuditConfig configures the audit log helper.
Audit rows are written via lifecycle hooks: AfterCreate, AfterUpdate, and AfterDelete. The hook fires inside the same transaction as the operation it audits, so partial writes are impossible — a rollback drops the audit row along with the change.
type AuthMDConfig ¶ added in v0.10.0
type AuthMDConfig struct {
// Markdown is the /auth.md body — the procedural manifest agents read
// (discover → register → claim → exchange → use → revoke). Host-authored.
Markdown string
// AgentAuth, when set, is merged as an `agent_auth` block into the
// /.well-known/oauth-authorization-server document (requires
// WithOAuthAuthorizationServer). Nil omits the block.
AgentAuth *AgentAuthBlock
}
AuthMDConfig configures /auth.md and the agent_auth discovery block.
type Battery ¶
type Battery interface {
// Name returns the unique battery identifier. Used for dependency
// resolution and logging.
Name() string
// Init wires the battery into the App. Called once during App.Start,
// before lifecycle hooks fire and after any batteries this one
// depends on have themselves initialized.
Init(app *App) error
}
Battery is the interface for heavyweight, lifecycle-aware modules that extend a GoFastr application (auth, search, cache, queue, etc.).
A Battery is a Plugin (Init(*App) error is the same shape) plus two things a plain Plugin doesn't have:
- Dependency declarations. Pass dependency names at RegisterBattery time and the framework topologically sorts the Init order so dependents see their dependencies wired first.
- Structured start/stop. Implement BatteryLifecycle to get per-battery OnStart(ctx) / OnStop(ctx) hooks, distinct from the App-wide app.OnStart / app.OnStop. The framework runs them in dependency order on Start and reverse-dependency order on Stop.
If neither of those applies, prefer Plugin — there is less ceremony and the Plugin/Battery distinction stays meaningful only for the modules that genuinely need it.
From Init the battery does everything by calling into the App — register routes via app.Router, middleware via app.Use, swap the logger via app.SetLogger, MCP tools via app.MCP, hooks via app.HookRegistry(name).RegisterHook(...), and so on. There are no optional interfaces for routes / middleware / hooks / tools — Init owns it all.
type BatteryLifecycle ¶
type BatteryLifecycle interface {
Battery
// OnStart is called after all batteries are initialized and before the
// HTTP server begins accepting connections. The context is cancelled
// when the app shuts down, so long-running workers should respect it.
// The first error aborts startup.
OnStart(ctx context.Context) error
// OnStop is called during graceful shutdown, after the HTTP server has
// stopped. Batteries stop in reverse dependency order (dependents
// first, then their dependencies).
OnStop(ctx context.Context) error
}
BatteryLifecycle is the optional interface for batteries that need to participate in the App's startup and shutdown sequence.
type BatteryManager ¶
type BatteryManager struct {
// contains filtered or unexported fields
}
BatteryManager manages registered batteries, resolves dependencies, and orchestrates initialization and lifecycle.
func NewBatteryManager ¶
func NewBatteryManager() *BatteryManager
NewBatteryManager creates a new BatteryManager.
func (*BatteryManager) All ¶
func (bm *BatteryManager) All() []Battery
All returns all registered batteries in dependency-resolved order.
func (*BatteryManager) Get ¶
func (bm *BatteryManager) Get(name string) (Battery, error)
Get retrieves a battery by name. Returns the Battery interface so callers can type-assert to the concrete type or any optional interface.
func (*BatteryManager) InitAll ¶
func (bm *BatteryManager) InitAll(app *App) error
InitAll initializes all batteries in dependency order. Called during App.Start before the HTTP server binds.
func (*BatteryManager) Names ¶
func (bm *BatteryManager) Names() []string
Names returns battery names in dependency-resolved order.
func (*BatteryManager) Register ¶
func (bm *BatteryManager) Register(b Battery, deps ...string) error
Register adds a battery with optional dependency declarations. Deps lists battery names that must be initialized before this one. Returns an error on duplicate name or unknown dependency.
type BoolColumn ¶
type BoolColumn = entity.BoolColumn
type Broker ¶ added in v0.31.0
type Broker struct {
// contains filtered or unexported fields
}
Broker is the production capability broker for the module reverse channel (design #37 §5). It implements ReverseBroker: it installs the host.* reverse-request handlers on each child's moduleproto.Peer and mints per-call delegation handles that let a reverse call re-attach the originating end-user request's caller context to the CRUD re-dispatch.
The capability model is module-grant ∩ caller-authority (design §5):
- The MODULE half is access.ScopeMatch of the derived required permission against the operator-approved grant view, run BEFORE any dispatch. The required permission is derived from the TRUSTED method + canonical host resource (entity name / "search" / event topic), NEVER from a child-supplied capability string (the confused-deputy control).
- The CALLER half is the CRUD chokepoint re-entered via crud.Redispatch through the router so owner/tenant/permission + token-scope re-run on the re-attached caller identity.
The CrossOwnerRead carve-out (design §5) is enforced on BOTH halves: a module never brokers data in a cross-owner/cross-tenant frame, so even a delegated end-user who legitimately holds CrossOwnerRead in their own session cannot exercise it through a module.
Fail-closed by construction: an unknown / expired / released delegation handle denies; an ambient (caller-less) reverse call carries no owner/tenant id so the CRUD owner/tenant gates refuse (errOwnerRequired / RequireTenant); a missing entity/search surface denies rather than silently returning empty.
func NewBroker ¶ added in v0.31.0
func NewBroker(router http.Handler, entities entity.Registry, events *event.EventBus, apiPrefix string, opts ...BrokerOption) *Broker
NewBroker constructs a capability broker. router/entities/events may be nil; a nil dependency makes the corresponding reverse surface deny (fail-closed) rather than silently succeed — the supervisor's NopBroker is the all-deny degenerate case. apiPrefix is the app's API prefix ("" or "/api") used to build entity CRUD re-dispatch paths.
func (*Broker) InstallHandlers ¶ added in v0.31.0
func (b *Broker) InstallHandlers(p *moduleproto.Peer, view ModuleGrantView)
InstallHandlers registers the six host.* reverse-request handlers (design §4.4) on p, each scoped to view. Handlers are registered once per child connection; the view captures the spawn's effective grant set + generation.
func (*Broker) MintDelegation ¶ added in v0.31.0
MintDelegation stashes a snapshot of the originating request under a random opaque handle and returns it with a release func. The supervisor attaches the handle to module.http's Caller block; the child echoes it on reverse host.* calls so the broker can re-attach THIS request's caller context to the CRUD re-dispatch. release() MUST be called when the parent call returns (including buffered-503 crash paths) so the in-memory table does not leak. A nil r is the ambient (caller-less) path — MintDelegation is still legal but returns an empty handle the broker treats as ambient.
type BrokerOption ¶ added in v0.31.0
type BrokerOption func(*Broker)
BrokerOption configures a Broker.
func WithBrokerClock ¶ added in v0.31.0
func WithBrokerClock(now func() time.Time) BrokerOption
WithBrokerClock injects the clock the handle-expiry check reads.
func WithBrokerHandleTTL ¶ added in v0.31.0
func WithBrokerHandleTTL(d time.Duration) BrokerOption
WithBrokerHandleTTL bounds the lifetime of a leaked-but-unreleased handle. Default 5m. Tests override to exercise expiry without real sleeps.
func WithBrokerPolicy ¶ added in v0.31.0
func WithBrokerPolicy(p *access.RolePolicy) BrokerOption
WithBrokerPolicy installs the app-wide RolePolicy so an ambient (caller-less) reverse call can resolve the module's synthetic `module/<name>` role against its operator-approved grants. Optional: when nil, ambient re-dispatch is anonymous and fails closed on any RBAC-gated entity (still safe — the owner/tenant gates refuse regardless).
func WithBrokerSearchEndpoint ¶ added in v0.31.0
func WithBrokerSearchEndpoint(h http.Handler) BrokerOption
WithBrokerSearchEndpoint wires the host.search.query re-dispatch target. Optional; nil → search denies after the perm gate.
type CachedResolver ¶ added in v0.30.0
type CachedResolver = access.CachedResolver
CachedResolver wraps a roles resolver with per-user TTL caching.
type ChildSpec ¶ added in v0.31.0
type ChildSpec struct {
// Descriptor is the operator-approved descriptor. The runner reads
// ArtifactPath / ArtifactSHA256 / TrustTier / Limits from it.
Descriptor ProcessModuleDescriptor
// InstanceID is the random per-spawn liveness nonce (design §4.7
// step 1). The runner does not interpret it; the supervisor passes it
// through to the handshake via its own state.
InstanceID string
// EffectiveGrants is the post-approval grant set for this spawn.
// Stored on the spec so the supervisor (which calls Start) keeps a
// single source of truth; the runner does not interpret it.
EffectiveGrants []access.Permission
// Generation is the store's desired_generation read at spawn. Stored
// on the spec for the same reason as EffectiveGrants.
Generation uint64
// Stderr is the bounded sink the child's stderr is drained into. If
// nil the runner constructs a default-capacity [moduleproto.RingSink]
// tagged for the module (so the supervisor can Tail recent output).
Stderr *moduleproto.RingSink
// ExtraEnv is a list of "KEY=VALUE" entries added on top of the
// default scrubbed allowlist (mirrors mcpclient.SpawnConfig.Env).
// These win over the allowlist on collision. Use only for values the
// caller knows; never as a back-channel for host secrets.
ExtraEnv []string
// InheritEnv is a list of host env var names to copy through beyond
// the default allowlist (mirrors mcpclient.SpawnConfig.InheritEnv).
// Every name is a deliberate allow decision.
InheritEnv []string
// ScratchDir is the per-module working directory. If empty the runner
// creates one under os.TempDir named "gofastr-module-<name>-<instance>".
// The supervisor usually supplies t.TempDir() in tests.
ScratchDir string
}
ChildSpec is the supervisor's spawn request. Every field is host-derived (operator-approved descriptor + the store's read generation + the per-spawn instance_id). The child never supplies these.
type ConformanceReport ¶ added in v0.31.0
type ConformanceReport struct {
Backend string
// Available reports whether the backend's wrapper tool was present
// and usable at probe time (bwrap/sandbox-exec on PATH, kernel
// features present). When false, every probe is Unreachable.
Available bool
// MissingReason explains why an unavailable backend is unavailable
// (e.g. "bwrap not on PATH", "unprivileged userns disabled").
MissingReason string
// Results has one entry per [ProbeID] in P1..P7 order.
Results []ProbeResult
}
ConformanceReport is the per-backend record of all seven probes. It is the artifact NewSandboxRunner consults to decide fail-closed and the artifact TestSandboxConformance emits.
func RunConformance ¶ added in v0.31.0
func RunConformance(ctx context.Context, b SandboxBackend, t testingTB) ConformanceReport
RunConformance executes the full P1–P7 probe suite against b on the current host and returns the report. When b is unavailable, every probe is recorded as Unreachable with the backend's MissingReason — this is NOT a pass; Conforms() returns false and the supervisor fail-closes.
t is used only for scratch-dir provisioning (t.TempDir); pass nil to use an os.MkdirTemp under the system temp dir (for non-test callers like the SandboxRunner constructor probing at startup).
func (ConformanceReport) Conforms ¶ added in v0.31.0
func (r ConformanceReport) Conforms() bool
Conforms reports whether the backend qualifies for an untrusted module per design §6: Available AND every probe P1–P7 passed. Any failure or unreachable disqualifies — there is no partial conformance, and the supervisor fail-closes rather than silently downgrading to TrustedProcessRunner.
func (ConformanceReport) Result ¶ added in v0.31.0
func (r ConformanceReport) Result(id ProbeID) ProbeResult
Result returns the ProbeResult for id, or a zero-value result if the probe was not run.
func (ConformanceReport) Summary ¶ added in v0.31.0
func (r ConformanceReport) Summary() string
Summary renders the report as a multi-line human-readable string for logs / test output. One line per probe: "P1 pass Distinct OS principal".
type CoordinatorOption ¶ added in v0.31.0
type CoordinatorOption func(*MigrationCoordinator)
CoordinatorOption configures a MigrationCoordinator.
func WithCoordinatorAdminDSN ¶ added in v0.31.0
func WithCoordinatorAdminDSN(dsn string) CoordinatorOption
WithCoordinatorAdminDSN supplies the URL-form Postgres DSN the per-module role DSN is derived from (same host/db, user=module_role, generated password). Required for the Postgres path; ignored on SQLite. This is the host's privileged DSN — keep it off any path reachable by untrusted callers.
func WithCoordinatorClock ¶ added in v0.31.0
func WithCoordinatorClock(now func() time.Time) CoordinatorOption
WithCoordinatorClock injects the clock Apply stamps MigrationsAppliedAt with. Tests override to avoid real time.
type CoordinatorPlan ¶ added in v0.31.0
type CoordinatorPlan struct {
Group string
Steps []PlannedStep
Warnings []string
}
CoordinatorPlan is the reviewable artifact Plan returns: the group the migrations will run under, the ordered steps with digests, and the install-time lint warnings (advisory only — the role is the real boundary, design §7 "no parse-allowlist").
type CoordinatorValidationError ¶ added in v0.31.0
type CoordinatorValidationError struct {
Field string
Rule string
// contains filtered or unexported fields
}
CoordinatorValidationError is the typed error Plan/Apply return for a group/digest/duplicate rule violation. Field + Rule let a CLI or install UI map it to a form, mirroring DescriptorValidationError.
func (*CoordinatorValidationError) Error ¶ added in v0.31.0
func (e *CoordinatorValidationError) Error() string
func (*CoordinatorValidationError) Is ¶ added in v0.31.0
func (e *CoordinatorValidationError) Is(target error) bool
Is lets callers errors.Is against the rule even when wrapped.
type CrudHandler ¶
type CrudHandler = crud.CrudHandler
type DBExecutor ¶
type DescriptorValidationError ¶ added in v0.31.0
type DescriptorValidationError struct {
Field string // descriptor field or "grants[N]"
Rule string // short rule id, e.g. "empty", "non_hex", "non_grantable"
// contains filtered or unexported fields
}
DescriptorValidationError is returned by ValidateProcessModuleDescriptor when a descriptor fails install-time validation. The error names the FIRST failing field/check so an operator sees an actionable cause; the [Field] and [Rule] let a UI map it to a form.
func (*DescriptorValidationError) Error ¶ added in v0.31.0
func (e *DescriptorValidationError) Error() string
func (*DescriptorValidationError) Is ¶ added in v0.31.0
func (e *DescriptorValidationError) Is(target error) bool
Is lets callers errors.Is against DescriptorValidationError if they wrap it.
type DesiredState ¶ added in v0.31.0
type DesiredState struct {
Module string
// DesiredGeneration is the monotonic desired-state counter. READ at
// spawn (not minted); bumped only on a grant change, revoke, or
// upgrade. The restart circuit is keyed to (module, generation).
DesiredGeneration uint64
// Enabled is the desired enable/disable state. The supervisor's
// reconcile loop reads this; the cache flip is what the route gate
// actually checks.
Enabled bool
// ArtifactSHA256 is the approved executable digest. Verified by the
// runner before exec.
ArtifactSHA256 string
// EffectiveGrants is the post-approval, post-carve-out grant set. A
// change here bumps DesiredGeneration.
EffectiveGrants []access.Permission
// MigrationsAppliedAt is nil until the migration coordinator has run
// pending migrations; the supervisor refuses Ready while nil for a
// module with a declared migration group.
MigrationsAppliedAt *time.Time
}
DesiredState is the authoritative desired-state row for one module (design §8). Every field here is operator-/host-authored; the child never writes any of it.
type DestructiveChangeError ¶
type DestructiveChangeError = migrate.DestructiveChangeError
type EntityConfig ¶
type EntityConfig = entity.EntityConfig
type EntityDeclaration ¶
type EntityDeclaration = entity.EntityDeclaration
type EventHandler ¶
type EventHandler = event.EventHandler
type ExecutableSHAMismatchError ¶ added in v0.31.0
ExecutableSHAMismatchError is returned when the executable's measured SHA-256 does not equal the descriptor pin. Terminal per design §3/§6: a swapped binary never reaches exec, and the supervisor transitions the module to Failed rather than retry.
func (*ExecutableSHAMismatchError) Error ¶ added in v0.31.0
func (e *ExecutableSHAMismatchError) Error() string
type ExportOption ¶ added in v0.20.0
type ExportOption func(*exportConfig)
ExportOption configures ExportData.
func WithExportPageSize ¶ added in v0.20.0
func WithExportPageSize(n int) ExportOption
WithExportPageSize sets the keyset page size (rows per SELECT). The default (1000) bounds memory for large tables. Non-positive falls back to the default.
func WithExportTime ¶ added in v0.20.0
func WithExportTime(t time.Time) ExportOption
WithExportTime stamps the manifest's created_at with the given time instead of time.Now. Pass a fixed value when deterministic output matters (tests, reproducible archives). Zero means "use time.Now().UTC()".
type FieldDeclaration ¶
type FieldDeclaration = entity.FieldDeclaration
type FloatColumn ¶
type FloatColumn = entity.FloatColumn
type ForeignKey ¶
type ForeignKey = migrate.ForeignKey
type GrantStore ¶ added in v0.20.0
type GrantStore = access.GrantStore
type GroupOption ¶
type GroupOption = routegroup.GroupOption
GroupOption is re-exported for routegroup configuration.
type HookGetPayload ¶
type HookGetPayload = hook.GetPayload
type HookListPayload ¶
type HookListPayload = hook.ListPayload
type HookRegistry ¶
type HookRegistry = hook.HookRegistry
type HookWhereClause ¶
type HookWhereClause = hook.WhereClause
type InMemoryModuleStore ¶ added in v0.17.0
type InMemoryModuleStore struct {
// contains filtered or unexported fields
}
InMemoryModuleStore is the default store for apps without a DB. State is lost on restart — modules re-enable on every boot.
func NewInMemoryModuleStore ¶ added in v0.17.0
func NewInMemoryModuleStore() *InMemoryModuleStore
NewInMemoryModuleStore creates an empty in-memory store.
func (*InMemoryModuleStore) Load ¶ added in v0.17.0
Load returns the current enable/disable map (possibly empty).
func (*InMemoryModuleStore) SetEnabled ¶ added in v0.17.0
SetEnabled persists the enabled state in memory.
type IncludeNode ¶
type IncludeNode = crud.IncludeNode
type ListOptions ¶
type ListOptions = crud.ListOptions
type ListResponse ¶
type ListResponse = crud.ListResponse
type Metrics ¶
type Metrics = middleware.Metrics
type MigrationCoordinator ¶ added in v0.31.0
type MigrationCoordinator struct {
// contains filtered or unexported fields
}
MigrationCoordinator runs a process module's migrations under the §7 isolation model. Construct one per Apply; it is short-lived (deploy job / CLI), never kept alive across the app's runtime. The supervisor refuses Ready while MigrationsAppliedAt is nil (design §7 step 4 + [migrationsPending]); this coordinator is what flips it.
func NewMigrationCoordinator ¶ added in v0.31.0
func NewMigrationCoordinator(store ProcessModuleStore, adminDB *sql.DB, opts ...CoordinatorOption) (*MigrationCoordinator, error)
NewMigrationCoordinator constructs a coordinator over the given store + admin pool. The admin pool MUST be the host-privileged connection (it creates schemas and roles); detect the dialect from it. EnsureSchema on the store is the caller's job (the app does it at RegisterProcessModule).
func (*MigrationCoordinator) Apply ¶ added in v0.31.0
func (c *MigrationCoordinator) Apply(ctx context.Context, desc ProcessModuleDescriptor, migs []ApprovedMigration) error
Apply validates the migration set (Plan), then runs it under the §7 isolation model. On success it stamps MigrationsAppliedAt and advances the store generation so the supervisor lets the module reach Ready.
Postgres: provisions a restricted per-module schema + role, opens a separate *sql.DB AUTHENTICATED AS that role (not SET ROLE down from a powerful session — design §7 load-bearing point 2), and runs Up under the advisory lock with the schema-local _migrations tracking table.
SQLite: there is no roles/GRANT/schemas boundary (design §7 / decision F). Untrusted modules are rejected loud (fail-closed). Trusted/dev-only modules run Up against the host pool — trusted-only, never a claim that groups make SQLite DDL safe.
func (*MigrationCoordinator) Dialect ¶ added in v0.31.0
func (c *MigrationCoordinator) Dialect() migrate.Dialect
Dialect reports the detected dialect (debug / introspection).
func (*MigrationCoordinator) Plan ¶ added in v0.31.0
func (c *MigrationCoordinator) Plan(desc ProcessModuleDescriptor, migs []ApprovedMigration) (*CoordinatorPlan, error)
Plan is the pure, DB-free validation of an approved migration set against the descriptor's group. It enforces (design §7):
- the descriptor MUST declare a named migration group (default-group migrations are rejected — a module cannot inject into the host's own group);
- every migration runs under that one group (group-equality);
- no duplicate (Version) within the set;
- each migration's computed SHA256(Up) matches its approved digest.
It also produces non-authoritative lint warnings (statements beyond plain CREATE TABLE / ALTER TABLE ADD COLUMN / CREATE INDEX) for operator review. The role is the real boundary; this lint never blocks (design §7 "no parse-allowlist").
type MigrationPlan ¶
type Module ¶ added in v0.17.0
type Module interface {
Battery
Manifest() ModuleManifest
}
Module is a Battery plus a manifest. Everything a module registers during Init (routes, entities, cron jobs, queue consumers, MCP tools) is attributed to the module, and a runtime enable/disable gate is enforced at dispatch time: disabled → its routes 404, its cron jobs and queue consumers skip, its MCP tools refuse.
type ModuleGrantView ¶ added in v0.31.0
type ModuleGrantView struct {
// Name is the module name (descriptor-supplied, operator-approved).
Name string
// Grants is the effective grant set (descriptor.requested ∩
// operator.approved), with the non-grantable carve-out (design §5)
// already applied at install time.
Grants []access.Permission
// Generation is the ProcessModuleStore's desired_generation read at
// spawn. The supervisor rejects a reverse call whose echoed generation
// no longer matches the store's current value — the revoke path.
Generation uint64
}
ModuleGrantView is the grant snapshot the broker enforces for one child connection. It is the supervisor's effective-grant set for the module at the spawn's read desired_generation — the binding the live stdio connection authenticates (design §5 "binding + revocation").
type ModuleInfo ¶ added in v0.17.0
type ModuleInfo struct {
Name string
Version string
Description string
DependsOn []string
MigrationGroup string
Enabled bool
EntityCount int
RouteCount int
ToolCount int
// ProcessState is the live [ProcessState] for a process module, or
// the empty string for an in-process module.
ProcessState string
// RestartCount is the number of unexpected exits in the current
// circuit window (process modules only).
RestartCount int
// ObservedGeneration is the child's current desired_generation view
// compared against the store's; non-zero only for process modules.
ObservedGeneration uint64
// InstanceID is the live spawn's liveness nonce (process modules only).
InstanceID string
// LastExit is the diagnostic label of the most recent child exit
// (process modules only). One of: "", "drained", "crashed: <err>",
// "lease-expired-drain", "failed: <reason>", "exited-cleanly-unexpected".
LastExit string
}
ModuleInfo is the introspection view of a registered module.
type ModuleLimits ¶ added in v0.31.0
type ModuleLimits struct {
// Deadline is the per-call ceiling for module.* requests. Default 10s;
// the descriptor may lower it.
Deadline time.Duration
// FrameBytes is the negotiated max_frame_bytes. Default 1 MiB; the
// descriptor may lower it. Must be ≤ [moduleproto.scannerMaxCap].
FrameBytes int
// Inflight is the maximum simultaneously in-flight module.* requests
// the host will issue. Default 32; the descriptor may lower it.
Inflight int
}
ModuleLimits is the per-child resource envelope an operator may narrow from the v1 defaults (design §4.4 / §8). The descriptor may LOWER a ceiling but never RAISE it — a value of 0 means "host default applies"; a non-zero value greater than the host default is rejected at install time.
type ModuleManager ¶ added in v0.17.0
type ModuleManager struct {
// contains filtered or unexported fields
}
ModuleManager tracks registered modules, their enable/disable state, and the attribution of routes/tools/entities to modules. It is the runtime gate that disabled-module enforcement reads on every request.
func NewModuleManager ¶ added in v0.17.0
func NewModuleManager(db *sql.DB, f fanout.Fanout) *ModuleManager
NewModuleManager creates a manager backed by the appropriate store. When db is non-nil a SQLModuleStore is used; otherwise in-memory. If db is non-nil and the SQL store cannot be created (e.g. CREATE TABLE fails), the error is stored and surfaced by loadFromStore so boot fails closed — a deliberately disabled module must not silently come back enabled on a broken store.
func (*ModuleManager) Disable ¶ added in v0.17.0
func (mm *ModuleManager) Disable(ctx context.Context, name string) error
Disable persists the new state and flips the cache. Refuses (fail closed) if any currently-enabled module lists name in DependsOn — no cascade. Serialized by toggleMu alongside Enable.
func (*ModuleManager) Enable ¶ added in v0.17.0
func (mm *ModuleManager) Enable(ctx context.Context, name string) error
Enable persists the new state and flips the cache. Refuses if any of the module's DependsOn is disabled. The check-then-act sequence (dependency check → store write → cache flip) is serialized by toggleMu so concurrent toggles cannot interleave to a forbidden state.
func (*ModuleManager) Enabled ¶ added in v0.17.0
func (mm *ModuleManager) Enabled(name string) bool
Enabled is the hot-path read the dispatch gates use. A single map read under RLock; absent modules are enabled by default.
func (*ModuleManager) List ¶ added in v0.17.0
func (mm *ModuleManager) List() []ModuleInfo
List returns introspection info for every registered module.
func (*ModuleManager) SetProcessCoordinator ¶ added in v0.31.0
func (mm *ModuleManager) SetProcessCoordinator(pc processModuleCoordinator)
SetProcessCoordinator installs the process-module coordinator. Called once by App wiring (RegisterProcessModule / a With… option); subsequent calls overwrite the prior coordinator. Passing nil disables the hook.
type ModuleManifest ¶ added in v0.17.0
type ModuleManifest struct {
// Version is an optional, informational version string.
Version string
// Description is a short, human-readable summary.
Description string
// DependsOn names other MODULES that must be enabled (and initialised)
// before this one. RegisterModule forwards this to BatteryManager as
// the battery dep list, so topo-sort orders module init.
DependsOn []string
// MigrationGroup defaults to the module name. It is an informational
// pointer to the core/migrate group (#33) the module owns — the
// framework does not enforce that it matches a registered migration
// group, but the modules doc describes the tie-in.
MigrationGroup string
}
ModuleManifest is the declarative metadata a Module carries alongside its Battery Init. Everything in it is informational — the framework uses DependsOn for battery init ordering (it doubles as the battery dep list), and the rest is surfaced through introspection.
type ModuleStore ¶ added in v0.17.0
type ModuleStore interface {
Load(ctx context.Context) (map[string]bool, error)
SetEnabled(ctx context.Context, name string, enabled bool) error
}
ModuleStore persists module enable/disable state across restarts. The default in-memory store is used when the app has no DB; an app built with WithDB gets the SQL store automatically.
type ModuleToolRegistry ¶ added in v0.31.0
type ModuleToolRegistry struct {
// contains filtered or unexported fields
}
ModuleToolRegistry implements ToolRegistrar against the host's *mcp.Server. One registry per App, shared across every process module; constructed in App.RegisterProcessModule and injected as the supervisor's ToolRegistrar. It also answers the composite call gate so disabled-module tools are omitted from tools/list (design §8).
func NewModuleToolRegistry ¶ added in v0.31.0
func NewModuleToolRegistry(server *mcp.Server, sup *ProcessModuleSupervisor) *ModuleToolRegistry
NewModuleToolRegistry constructs a registry over server + sup. Either may be nil for tests that only exercise [RegisterTools]'s bookkeeping (a nil server makes RegisterTools track but not install; a nil sup makes dispatch return errModuleToolUnavailable).
func (*ModuleToolRegistry) GateForModule ¶ added in v0.31.0
func (r *ModuleToolRegistry) GateForModule(moduleName string) error
GateForModule is the module-tool half of the composite call gate. Disabled module → non-nil error (tool OMITTED from tools/list and refused by tools/call, indistinguishable from uninstalled — design §8 "not-enabled → omitted+refused"). Enabled module → nil (tool LISTED; the handler enforces the Ready layer and returns a retryable error when the child is not yet serving). This split mirrors the route gate's Enabled-only 404 vs the proxy handler's 503.
func (*ModuleToolRegistry) ModuleFor ¶ added in v0.31.0
func (r *ModuleToolRegistry) ModuleFor(toolName string) (string, bool)
ModuleFor reports which module owns a namespaced tool name (or "" / ok=false for a non-module tool). The composite call gate consults this to route module tools to [GateForModule] and leave everything else on the existing in-process gate.
func (*ModuleToolRegistry) RegisterTools ¶ added in v0.31.0
func (r *ModuleToolRegistry) RegisterTools(moduleName string, tools []moduleproto.Tool) error
RegisterTools installs each tool under `module.<name>.<tool>` after the supervisor has verified the tool list against the descriptor digests. Idempotent across respawns: a tool already tracked for THIS module is left in place (its MCP handler dispatches dynamically, so the stale handler is still correct). A name already owned by a DIFFERENT module, or shadowed by a non-module tool already in the server, is a hard collision and returns an error.
func (*ModuleToolRegistry) UnregisterTools ¶ added in v0.31.0
func (r *ModuleToolRegistry) UnregisterTools(moduleName string) error
UnregisterTools is a tracking-only no-op in v1: core/mcp has no tool-removal API, so the tool stays in the server. Visibility is gated by the composite call gate (a disabled/uninstalled module's tools are omitted + refused). owner[] is intentionally kept populated so GateForModule keeps refusing the tool until the module re-registers; clearing it here would let the tool leak back through the in-process gate's "no owner → pass" path. Documented as a known limitation for v1.
type Mountable ¶
Mountable is anything that can register routes on the framework's router. UI hosts, admin panels, websocket pubsub layers, etc. all satisfy this interface and are attached via App.Mount.
type NopBroker ¶ added in v0.31.0
type NopBroker struct{}
NopBroker is the no-op ReverseBroker: it installs handlers that deny every reverse host.* call with a capability error, and mints an empty delegation handle whose release is a no-op. It is the supervisor default until the real broker lands (design §5) and the test fake when a test does not exercise the reverse channel.
Fail-closed: a child that issues host.entity.query / host.search.query / host.event.emit under a NopBroker receives a JSON-RPC error response for every call — no host data is brokered, ambient or delegated.
func (NopBroker) InstallHandlers ¶ added in v0.31.0
func (NopBroker) InstallHandlers(p *moduleproto.Peer, _ ModuleGrantView)
InstallHandlers registers deny-all handlers on p for every host.* method in the moduleproto catalog. The view is accepted but unused — a NopBroker grants nothing by construction.
func (NopBroker) MintDelegation ¶ added in v0.31.0
MintDelegation returns an empty handle and a no-op release. Under NopBroker no reverse call can succeed anyway, so the handle is unused; the empty string is a legal Caller.Delegation value (omitted on the wire).
type OAuthAuthorizationServerConfig ¶ added in v0.10.0
type OAuthAuthorizationServerConfig struct {
Issuer string // REQUIRED: issuer identifier URL
AuthorizationEndpoint string
TokenEndpoint string
IntrospectionEndpoint string
UserinfoEndpoint string
JwksURI string
ScopesSupported []string
ResponseTypesSupported []string
GrantTypesSupported []string
TokenEndpointAuthMethodsSupported []string
}
OAuthAuthorizationServerConfig configures /.well-known/oauth-authorization-server (RFC 8414). Relevant when the host acts as an OAuth2/OpenID issuer (battery/auth is a client by default, so this is opt-in).
type OAuthProtectedResourceConfig ¶ added in v0.10.0
type OAuthProtectedResourceConfig struct {
// Resource is the protected resource's identifier: an absolute https
// URL with no fragment (a query component is discouraged but allowed).
// REQUIRED by RFC 9728.
Resource string
// AuthorizationServers lists OAuth authorization-server issuer
// identifiers (per RFC 8414) whose tokens this resource accepts.
AuthorizationServers []string
// ScopesSupported lists the scope values accepted in access requests
// to this resource.
ScopesSupported []string
// BearerMethodsSupported lists how a bearer token may be presented:
// "header", "body", and/or "query" (RFC 6750). Defaults to ["header"].
BearerMethodsSupported []string
// JWKSURI is the https URL of the resource's JWK Set (public signing
// keys the resource uses to sign responses, if any).
JWKSURI string
// ResourceName is a human-readable name for display.
ResourceName string
// ResourceDocumentation is a https URL with developer info.
ResourceDocumentation string
// ResourcePolicyURI is a https URL describing data-use policy.
ResourcePolicyURI string
// ResourceTOSURI is a https URL describing terms of service.
ResourceTOSURI string
}
OAuthProtectedResourceConfig configures the /.well-known/oauth-protected-resource endpoint (RFC 9728).
type Permission ¶
type Permission = access.Permission
type PlannedStep ¶ added in v0.31.0
type PlannedStep struct {
Version uint64
Name string
Up string
Down string
Computed string // SHA256(Up), hex
Approved string // ApprovedMigration.SHA256
LintHints []string
}
PlannedStep is one migration in a CoordinatorPlan, with its computed digest and any non-authoritative lint warnings the operator should review before approving Apply.
type Plugin ¶
type Plugin interface {
// Name returns the unique plugin identifier.
Name() string
// Init wires the plugin into the App. Called once during App.Start.
Init(app *App) error
}
Plugin is the interface for lightweight GoFastr extensions — anything that needs to register routes, middleware, hooks, or MCP tools but has no dependency on other modules and no structured start/stop lifecycle of its own.
Plugins have a single integration point: Init(app). From there a plugin does everything it needs by calling into the App — register routes via app.Router, add middleware via app.Use, swap the logger via app.SetLogger, register MCP tools via app.MCP, attach hooks via app.HookRegistry(name).RegisterHook(...), and so on.
There are no optional interfaces. The router resolves middleware late-bound, so middleware added from Init wraps routes registered before the plugin loaded — there is no ordering footgun to dodge.
When to pick Plugin vs Battery
- Plugin: stateless or self-contained; no dependency on other modules; uses app.OnStart / app.OnStop if it needs lifecycle.
- Battery: depends on another module being initialised first (e.g. auth needs the user store wired before login), OR needs its own structured OnStart/OnStop distinct from the App-wide hooks (e.g. background queue workers with their own ctx).
Both share the Init(*App) shape; Battery just adds dependency declarations and a separate BatteryLifecycle. Most extensions should start as Plugin and graduate to Battery only when one of those two conditions appears.
type PluginManager ¶
type PluginManager struct {
// contains filtered or unexported fields
}
PluginManager manages registered plugins.
func NewPluginManager ¶
func NewPluginManager() *PluginManager
NewPluginManager creates a new plugin manager.
func (*PluginManager) All ¶
func (pm *PluginManager) All() []Plugin
All returns all registered plugins in order.
func (*PluginManager) Get ¶
func (pm *PluginManager) Get(name string) (Plugin, error)
Get retrieves a plugin by name.
func (*PluginManager) InitAll ¶
func (pm *PluginManager) InitAll(app *App) error
InitAll initializes all plugins in registration order.
Wraps each Init in a deferred recover so a panic — most commonly `http: multiple registrations for ...` from a duplicate route pattern — gets tagged with the offending plugin's name. Without this the panic surfaces deep in ServeMux with no context about which plugin registered the conflicting route.
Tracks per-plugin init state so that a retry after partial failure (App.InitPlugins rolls back its global latch on error) only re-runs plugins that haven't already applied side effects. Without this a retry would re-Register the routes the successful plugins already added and panic on the ServeMux duplicate-pattern check.
func (*PluginManager) Names ¶
func (pm *PluginManager) Names() []string
Names returns the names of all registered plugins in registration order.
func (*PluginManager) Register ¶
func (pm *PluginManager) Register(plugin Plugin) error
Register adds a plugin to the manager. Returns an error if a plugin with the same name is already registered, or if the name is invalid (empty, whitespace-only, contains a control character, or longer than maxModuleNameLen).
type ProbeID ¶ added in v0.31.0
type ProbeID int
ProbeID names one of the §6 sandbox properties P1–P7. The numeric value matches the design's table numbering so a log line "probe=P1" is self-documenting.
const ( // ProbeDistinctPrincipal is P1: the child runs as a distinct OS // principal from the host (uid/SID ≠ host) and cannot signal or // /proc-read host pids. ProbeDistinctPrincipal ProbeID = iota + 1 // ProbeNoInheritedSecret is P2: a canary planted in the host env and a // planted secret file pre-spawn are both invisible to the child. // (Baseline hygiene: empty-default env allowlist.) ProbeNoInheritedSecret // ProbeNoInheritedFD is P3: a host fd opened pre-spawn to a secret is // not inherited — the child enumerating fds > 2 finds none. // (Baseline hygiene: ExtraFiles=nil.) ProbeNoInheritedFD // ProbeNoNetworkEgress is P4: connect() to loopback, a public IP, and // the cloud-metadata IP all fail. (Backend-enforced.) ProbeNoNetworkEgress // ProbeFilesystemConfinement is P5: the child can read only its // package; writes land only under scratch; host tree/$HOME/secrets // are unreadable. (Backend-enforced.) ProbeFilesystemConfinement // ProbeResourceLimits is P6: a fork-bomb is capped, an OOM kills the // child (not the host), CPU is throttled, fds are capped. // (Backend-enforced via cgroup/Job Object.) ProbeResourceLimits // ProbeNoPrivReEscalation is P7: the child cannot setuid up, cannot // gain new caps, and no-new-privileges is in effect. // (Backend-enforced.) ProbeNoPrivReEscalation )
type ProbeResult ¶ added in v0.31.0
type ProbeResult struct {
ID ProbeID
Status ProbeStatus
// Detail is a short human-readable note: why a breach succeeded, why
// the probe was unreachable, or the denial observed on pass. Surfaced
// in the [ConformanceReport] for operator diagnostics and the CI
// skip/fail message.
Detail string
}
ProbeResult is the outcome of running one ProbeID against one SandboxBackend on the current host.
type ProbeStatus ¶ added in v0.31.0
type ProbeStatus int
ProbeStatus is the outcome of one probe under one backend on one host.
const ( // ProbeStatusUnknown is the zero value — a probe that has not been run. ProbeStatusUnknown ProbeStatus = iota // ProbeStatusPass means the sandbox DENIED the forbidden action: the // child attempted it and was refused. This is the only status that // counts toward conformance. ProbeStatusPass // ProbeStatusFail means the child BREACHED: the forbidden action // succeeded. The sandbox is not enforcing this property on this host. ProbeStatusFail // ProbeStatusUnreachable means the probe could not be meaningfully run // (backend unavailable, host lacks the prerequisite feature, the // backend's wrapper tool is missing). Unreachable ⇒ NOT conforming; // the distinction from Fail is diagnostic only. ProbeStatusUnreachable )
func (ProbeStatus) String ¶ added in v0.31.0
func (s ProbeStatus) String() string
String renders a ProbeStatus for logs and test output.
type ProcessModuleDescriptor ¶ added in v0.31.0
type ProcessModuleDescriptor struct {
// Name is the module's operator-approved unique name. Must match
// [moduleIdentPattern]; same namespace as in-process modules.
Name string
// Version is the operator-approved semantic version (informational;
// also round-tripped at handshake).
Version string
// ArtifactPath is the filesystem path to the approved executable. The
// runner verifies the executable's SHA-256 equals ArtifactSHA256
// BEFORE exec (verify-then-exec, design §4.6).
ArtifactPath string
// ArtifactSHA256 is the hex-encoded SHA-256 of the executable at
// ArtifactPath. Non-empty hex; verified before exec and never re-read
// from the child.
ArtifactSHA256 string
// SurfaceSHA256 is the hex-encoded SHA-256 of the canonical surface
// descriptor (routes + tool list + requested permissions). The child's
// handshake result echoes this; mismatch is terminal.
SurfaceSHA256 string
// Routes is the declared HTTP surface, host-registered behind the
// existing module route gate (Enabled → 404, indistinguishable from
// uninstalled).
Routes []RouteDeclaration
// Tools is the optional MCP tool surface (design §5.1). Empty means
// the module exposes no tools.
Tools []ToolDigest
// RequestedGrants is the verbatim resource:verb list the operator
// reviews at install (design §5). Effective grants =
// RequestedGrants ∩ ApprovedGrants, with the non-grantable carve-out
// applied. Capped at 32 entries.
RequestedGrants []access.Permission
// TrustTier selects the runner. TrustUntrusted requires a probe-
// passing *SandboxRunner (else Register fails closed).
TrustTier TrustTier
// MigrationGroup is the #33 migration group name this module owns
// (informational this wave; the migration coordinator lands later).
MigrationGroup string
// Limits narrows the host defaults. Zero values mean "default applies".
Limits ModuleLimits
}
ProcessModuleDescriptor is the content-addressed, operator-approved description of a process-isolated third-party module (design §3 decision B). Every field here is authoritative at runtime: the child's handshake only *cross-checks* digests, never supplies values. A mismatch on digest / identity / extra grant is terminal Failed (no restart loop).
type ProcessModuleInfo ¶ added in v0.31.0
type ProcessModuleInfo struct {
Name string
Version string
State ProcessState
DesiredGeneration uint64
ObservedGeneration uint64
InstanceID string
RestartCount int
CircuitOpen bool
LastExit string
TrustTier TrustTier
RouteCount int
ToolCount int
LeaseFailing bool
}
ProcessModuleInfo is the introspection view of one supervised module (design §8). Operator-only; surfaced via the existing ModuleInfo extension. Nil/zero for in-process modules.
type ProcessModuleStore ¶ added in v0.31.0
type ProcessModuleStore interface {
// EnsureSchema creates the desired-state and heartbeat tables if
// absent. Idempotent; safe to call on every boot.
EnsureSchema(ctx context.Context) error
// Install writes a new desired-state row for module. Returns
// [ErrModuleInstalled] if a row already exists — re-install is an
// explicit generation bump via [SetEffectiveGrants] /
// [BumpGeneration].
Install(ctx context.Context, d DesiredState) error
// GetDesired returns the desired-state row for module. Returns
// [ErrNoDesiredRow] if no row exists, and the underlying SQL error if
// the store is unreachable (the lease-enforcing read path).
GetDesired(ctx context.Context, module string) (DesiredState, error)
// ListDesired returns every desired-state row, ordered by module name.
// Used by introspection / a coordinator process.
ListDesired(ctx context.Context) ([]DesiredState, error)
// SetEnabled toggles enabled WITHOUT bumping generation (design §8:
// enable/disable is not a revoke lever; the cache flip is the gate).
SetEnabled(ctx context.Context, module string, enabled bool) error
// BumpGeneration atomically increments desired_generation and returns
// the new value. The revoke / upgrade lever: every replica's reconcile
// loop sees the higher value on its next GetDesired read.
BumpGeneration(ctx context.Context, module string) (uint64, error)
// SetEffectiveGrants replaces the effective grant JSON AND bumps
// generation (§5: a grant change is a generation change). Returns the
// new generation.
SetEffectiveGrants(ctx context.Context, module string, grants []access.Permission) (uint64, error)
// SetMigrationsAppliedAt records the timestamp pending migrations
// finished at. Nil clears it.
SetMigrationsAppliedAt(ctx context.Context, module string, at *time.Time) error
// RecordHeartbeat upserts a replica heartbeat row (module, replica_id)
// with observed_generation, phase, and updated_at = now. The TTL
// semantics are READ-side (LiveReplicas): a row older than 3× the
// heartbeat interval is treated as a dead replica, not a blocker.
RecordHeartbeat(ctx context.Context, module, replicaID string, observedGen uint64, phase string) error
// LiveReplicas returns heartbeat rows for module whose updated_at is
// within now-ttl..now. Rows older than ttl are dead and excluded.
LiveReplicas(ctx context.Context, module string, ttl time.Duration) ([]ReplicaHeartbeat, error)
// DeleteHeartbeat removes a replica's heartbeat row. Called on
// graceful drain so the next LiveReplicas reflects the departure.
DeleteHeartbeat(ctx context.Context, module, replicaID string) error
}
ProcessModuleStore is the SQL-backed coordination substrate for process-isolated modules (design §8 "coordination substrate"). It persists the authoritative desired state and a replica-liveness registry on top of the same *sql.DB the app already uses, via an idempotent CREATE TABLE IF NOT EXISTS schema (mirrors battery/auth's EnsureSchema pattern — NOT a #33 migrate group; this is host coordination state, not module-owned DDL).
It works on SQLite and Postgres. DDL is chosen per-dialect at SQLProcessModuleStore.EnsureSchema time.
State lease vs in-process fail-open (design §8) ¶
In-process module toggling [ModuleManager.handleRemoteToggle] FAILS OPEN on a store-read error (logs WARN, keeps cache). For a third-party module under revoke that is a security hole, so process modules INVERT it: the supervisor (above this store) holds a short-TTL state lease per replica; if it cannot refresh [GetDesired] past the lease TTL it drains children and serves 503/404. The lease policy lives in the supervisor, not here — this store is just rows. The divergence is documented at [ProcessModuleSupervisor.refreshDesired] and tested by the "store_unreachable_drains" test.
Revoke / upgrade lever ¶
Revoke (a grants change) and upgrade (an artifact change) are both expressed as a desired_generation bump: [BumpGeneration] atomically increments the row's counter and every replica's reconcile loop sees the higher value on its next [GetDesired] read. A grant-set change via [SetEffectiveGrants] also bumps generation (§5: a grant change is a generation change).
type ProcessModuleSupervisor ¶ added in v0.31.0
type ProcessModuleSupervisor struct {
// contains filtered or unexported fields
}
ProcessModuleSupervisor owns the per-module-per-replica state machine (design §8). One supervisor per App; multiple supervisors (one per replica) share the same ProcessModuleStore for cross-replica convergence.
func NewProcessModuleSupervisor ¶ added in v0.31.0
func NewProcessModuleSupervisor(cfg SupervisorConfig) (*ProcessModuleSupervisor, error)
NewProcessModuleSupervisor constructs a supervisor. It does NOT call EnsureSchema — that is the app wiring's job (so the schema-creation error surfaces where the operator expects it). The supervisor launches its per-module loops lazily as modules are registered.
func (*ProcessModuleSupervisor) BumpGeneration ¶ added in v0.31.0
BumpGeneration is the upgrade lever (design §8): increments the desired generation and wakes every replica's reconcile loop. The supervisor will drain the old child and spawn a fresh one for the new generation.
func (*ProcessModuleSupervisor) Close ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Close(ctx context.Context) error
Close stops every supervise loop, drains every child with a short deadline, and waits for the loops to exit. Idempotent. It does NOT close the store (the store is shared across the app and closed elsewhere).
func (*ProcessModuleSupervisor) Closed ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Closed() bool
Closed reports whether Close has been called.
func (*ProcessModuleSupervisor) DeclaredRoutes ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) DeclaredRoutes(name string) []RouteDeclaration
DeclaredRoutes returns the descriptor's declared routes for the host wiring to iterate when registering proxy routes with the router.
func (*ProcessModuleSupervisor) Disable ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Disable(ctx context.Context, name string) error
Disable marks a module desired-disabled in the store and wakes its supervise loop (which drains the child).
func (*ProcessModuleSupervisor) Drain ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Drain(ctx context.Context) error
Drain is the lifecycle.Drainer entry point. It drains every child concurrently (design §8: a single shared 30s budget across the app, so a per-child 30s drain applied serially to N children would blow the budget). Each child gets min(DrainPerModule, remaining shared budget).
func (*ProcessModuleSupervisor) Enable ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Enable(ctx context.Context, name string) error
Enable marks a module desired-enabled in the store and wakes its supervise loop. Returns ErrNoDesiredRow (wrapped) if the module is not registered.
func (*ProcessModuleSupervisor) Info ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Info(name string) (ProcessModuleInfo, error)
Info returns the introspection snapshot for name, or ErrNoDesiredRow (wrapped) if the module is not registered.
func (*ProcessModuleSupervisor) List ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) List() []ProcessModuleInfo
List returns introspection for every registered process module, ordered by name.
func (*ProcessModuleSupervisor) ProxyHandler ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) ProxyHandler(name, routeID string) http.Handler
ProxyHandler returns the http.Handler the host router mounts for one declared route. The route gate (Enabled) has ALREADY run by the time this handler is invoked — if the module is disabled, the route 404'd upstream and this handler was never reached.
This handler implements the SECOND layer of the §8 two-layer gate: the Ready check. Enabled-but-not-Ready → 503 + Retry-After (design decision D). On Ready it proxies the request via module.http, FULLY BUFFERING the response before committing any headers (the buffered-503 guarantee: a child that dies mid-call yields a buffered 503, never a truncated 200).
name is the module name; routeID is the descriptor's stable route id.
func (*ProcessModuleSupervisor) Reconcile ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Reconcile(name string)
Reconcile pokes the named module's supervise loop. It is the single entry point the three reconcile sources funnel into (local enable/ disable, handleRemoteToggle after its store re-read, periodic generation poll — design §8). Safe to call for unknown names (no-op).
func (*ProcessModuleSupervisor) Register ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Register(ctx context.Context, d ProcessModuleDescriptor, approved ApprovedGrants) (RegisterResult, error)
Register validates the descriptor, installs a desired-state row in the store, and registers the module for supervision. It is idempotent on the descriptor — calling it twice for the same name returns ErrModuleInstalled (the store row already exists). The supervisor's per-module loop is started lazily by ProcessModuleSupervisor.StartLoops or implicitly by ProcessModuleSupervisor.Enable.
The caller is responsible for route registration (attributing routes to the module name so the existing route gate 404s when disabled). The supervisor exposes [ProcessModuleSupervisor.Routes] for the host wiring to iterate when it calls the router.
func (*ProcessModuleSupervisor) RevokeGrants ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) RevokeGrants(ctx context.Context, name string, grants []access.Permission) (uint64, error)
RevokeGrants replaces the effective grants AND bumps generation — every replica's next reconcile observes the higher generation and restarts the child with the narrowed view (design §5 "binding + revocation"). Returns the new generation.
func (*ProcessModuleSupervisor) Slot ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) Slot(name string) *moduleSlot
Slot returns the live slot for name, or nil if unregistered. Used by the host wiring to attribute routes / consult state.
func (*ProcessModuleSupervisor) StartLoops ¶ added in v0.31.0
func (s *ProcessModuleSupervisor) StartLoops()
StartLoops launches every registered module's supervise loop and the global periodic poll. Idempotent; called by the app wiring at Start. After StartLoops, a subsequently-Registered module has its loop started by Enable (which itself wakes the slot).
type ProcessState ¶ added in v0.31.0
type ProcessState int
ProcessState is the supervisor's per-module state (design §8). The state machine is per module, per replica. External HTTP surface sees only the 404-vs-503 split (Enabled → 404 when disabled; enabled-but-not-Ready → 503 + Retry-After); operator introspection distinguishes the rest.
const ( // StateAbsent: the module is not registered with this supervisor. StateAbsent ProcessState = iota // StateInstalledDisabled: registered, desired disabled. The route gate // 404s; no child is running. StateInstalledDisabled // StateStarting: spawn in progress (child exec'd, not yet // handshaked). Proxy returns 503 + Retry-After. StateStarting // StateHandshaking: handshake in progress. Proxy returns 503. StateHandshaking // StateReady: child is live and ready; proxy forwards module.http. StateReady // StateCrashed: unexpected exit observed while desired-enabled. The // supervisor will charge the restart circuit and transition to // Backoff. Proxy returns 503. StateCrashed // StateBackoff: waiting for the backoff delay before the next restart // attempt. Proxy returns 503. StateBackoff // StateDrainingDisable: desired disabled mid-call; the supervisor is // finishing in-flight leases (deadline-bounded) before killing the // child and transitioning to InstalledDisabled. The route gate still // 404s new requests (the cache flipped first). StateDrainingDisable // StateDrainingUpgrade: desired generation advanced mid-call; the // supervisor is draining the OLD child before spawning the new one. // Stays Enabled (no 404); proxy returns 503 + Retry-After. StateDrainingUpgrade // StateFailed: terminal. An integrity fault (SHA mismatch, handshake // digest mismatch, protocol negotiation failure) — a restart cannot // fix a bad artifact. The module stays 404 (disabled) or 503 (enabled) // until the operator explicitly bumps generation with a corrected // artifact. The restart circuit is NOT charged. StateFailed )
func (ProcessState) String ¶ added in v0.31.0
func (s ProcessState) String() string
String makes ProcessState log-friendly and operator-readable.
type ReadinessCheck ¶
ReadinessCheck is a named probe run by GET /readyz. Implementations must be fast (sub-second), idempotent, and safe to invoke concurrently. Return a non-nil error to mark the app "not ready" — clients (load balancers, k8s, Fly health checks) treat a 503 from /readyz as a signal to stop routing traffic.
type ReadinessRegistrar ¶
type ReadinessRegistrar interface {
RegisterReadinessChecks(app *App)
}
ReadinessRegistrar is the optional interface plugins and batteries implement to contribute checks. The framework's plugin/battery machinery probes for it during InitPlugins and calls RegisterReadinessChecks before health endpoints mount.
The method is deliberately named RegisterReadinessChecks (plural) to avoid colliding with App.RegisterReadiness(name, fn) — the two are different surfaces, and embedding *App into a battery would otherwise produce an ambiguous selector.
type ReadinessResponse ¶
type ReadinessResponse struct {
Status string `json:"status"`
Checks []ReadinessResult `json:"checks"`
}
ReadinessResponse is the JSON shape returned by /readyz.
type ReadinessResult ¶
type ReadinessResult struct {
Name string `json:"name"`
Status string `json:"status"` // "ok", "error", or "timeout"
Error string `json:"error,omitempty"`
DurMS int64 `json:"durationMs"`
}
ReadinessResult is one row in the /readyz response.
type Ref ¶ added in v0.30.0
Ref, Decision and Decider form the resource-scoped decision seam — "member can edit project 42" — consulted before the role policy.
type RegisterResult ¶ added in v0.31.0
type RegisterResult struct {
Descriptor ProcessModuleDescriptor
EffectiveGrants []access.Permission
Generation uint64
}
RegisterResult is the return value of ProcessModuleSupervisor.Register: the validated descriptor, the computed effective grants, and the initial desired-generation the store row was written at.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry stores and retrieves Entity definitions by name. It is safe for concurrent use.
func (*Registry) All ¶
All returns a copy of the map of all registered entities. Map iteration order is randomised by Go; use AllSorted() for stable iteration in code paths that emit order-sensitive output.
func (*Registry) AllSorted ¶
AllSorted returns every registered entity in alphabetical order by name. Use this whenever the iteration order affects bytes-on-the-wire (OpenAPI tag emission, LLM-markdown, codegen output) so the same registry produces the same artefact across restarts.
func (*Registry) Get ¶
Get retrieves an Entity by name. Returns an error if no entity with that name is registered.
type RelationType ¶
type RelationType = entity.RelationType
type ReplicaHeartbeat ¶ added in v0.31.0
type ReplicaHeartbeat struct {
Module string
ReplicaID string
Generation uint64
Phase string
UpdatedAtMs int64
}
ReplicaHeartbeat is one replica's liveness row for a module (design §8).
type ReverseBroker ¶ added in v0.31.0
type ReverseBroker interface {
// InstallHandlers registers the host.* reverse-request handlers on p
// for the lifetime of this child connection. The handler set is scoped
// to view: a reverse call's derived required permission must be in the
// module-grant view (intersected with the caller's authority on the
// delegated path), else the broker denies before re-dispatch. The
// CrossOwnerRead / cross-tenant carve-out (design §5) is enforced on
// BOTH the module-grant path and the delegated-caller path.
InstallHandlers(p *moduleproto.Peer, view ModuleGrantView)
// MintDelegation issues an in-memory, replica-local opaque handle that
// the supervisor attaches to a proxied call's [moduleproto.Caller].
// The child echoes it on reverse host.* calls so the broker re-attaches
// the originating request's caller context to the internal re-dispatch.
// The returned release func MUST be invoked when the parent call
// completes (including buffered-503 crash paths) so the handle table
// does not leak. r may be nil for ambient (caller-less) module work.
MintDelegation(r *http.Request, parentCallID uint64) (handle string, release func())
}
ReverseBroker installs the host.* reverse-request handlers on a child's moduleproto.Peer and mints per-request delegation handles. It is the capability broker for the module's reverse channel (design §5).
The supervisor CONSUMES this interface; a later wave IMPLEMENTS it against the CRUD re-dispatch chokepoint (framework/crud/mcp.go's machinery, plus the access.ScopeMatch module-grant pre-filter and the CrossOwnerRead carve-out). Until that lands the supervisor wires NopBroker, which denies every reverse call with a capability error — fail-closed by default.
The shape is deliberately small and stable: install handlers once per child connection (handing the broker the live Peer + the child's grant view), and mint a delegation handle per inbound proxied call so reverse calls can re-attach the originating request's caller context.
type Role ¶ added in v0.16.0
type Role string
Role selects which responsibilities a single binary assumes at boot. One binary, role picked at deploy time — so background load (cron, queue workers, the outbox relay) can run in a dedicated process that doesn't share a listener with request serving.
Resolution precedence (see resolveRole): WithRole > the GOFASTR_ROLE env var > RoleAll. An unknown value in either place fails loudly in NewApp — a typo'd role must never silently run the wrong workload.
const ( // RoleAll is the default: serve HTTP AND run background consumers // (cron, queues, the outbox relay). Exactly today's behavior — zero // change for existing apps. RoleAll Role = "all" // RoleServe runs the full HTTP surface (router, auto-migrate, seeds, // plugins, batteries) but does NOT start worker-scoped consumers: // AddCron/AddQueue registrations and the outbox relay are skipped, so // a serve-only process never starts — nor later tries to drain — a // scheduler it never owned. Plain OnStart hooks still run; gate your // own via App.Role(). RoleServe Role = "serve" // RoleWorker runs background consumers (cron, queues, outbox relay) // and binds addr, but serves ONLY the health surface (/healthz + // /readyz). It does NOT mount the app router, entity CRUD, OpenAPI, // docs, admin, or well-known discovery routes. Auto-migrate, seeds, // plugins, and batteries still run: migrations take their own advisory // lock and seeds take a SEPARATE one, so either process type may boot // first without racing schema or seed writes. RoleWorker Role = "worker" )
type RolePolicy ¶
type RolePolicy = access.RolePolicy
type RoleWithOrigin ¶ added in v0.30.0
type RoleWithOrigin = access.RoleWithOrigin
RoleWithOrigin labels an effective role with where it came from ("direct" vs "resolved") for the admin users screen.
type RouteDeclaration ¶ added in v0.31.0
type RouteDeclaration struct {
// ID is the stable, descriptor-local route identifier the host places
// in [moduleproto.HTTPRequestParams.RouteID]. It lets the child
// dispatch by an opaque key without re-deriving method+path. IDs must
// be unique within a descriptor and match [idPattern].
ID string
// Method is the HTTP method (GET, POST, …). The host marshals it into
// [moduleproto.HTTPRequestParams.Method].
Method string
// Path is the route pattern (e.g. "/items/:id"). The host parses path
// parameters and forwards them in
// [moduleproto.HTTPRequestParams.PathParams]. Path must be non-empty
// and begin with "/".
Path string
}
RouteDeclaration is one operator-approved HTTP route a process module exposes. The descriptor is the source of truth (design §3 decision B); the child cannot add, rename, or reshape routes at runtime — the handshake cross-checks surface_sha256, and the host proxies only RouteIDs present here.
type RouteGroup ¶
type RouteGroup = routegroup.RouteGroup
RouteGroup is the App-level route group abstraction. Created via App.Group().
type Runner ¶ added in v0.31.0
type Runner interface {
// Start spawns the child per spec and returns the live handle.
// Canceling ctx cancels the spawn (kills a half-spawned child); it does
// NOT cancel the child's lifetime — the supervisor owns teardown via
// [RunningChild.CloseStdin] / [RunningChild.Kill] / [RunningChild.Wait].
Start(ctx context.Context, spec ChildSpec) (RunningChild, error)
}
Runner is the seam a process module's spawn path plugs into (design §6, decision C). Wave 2a ships exactly one implementation, TrustedProcessRunner; the next wave slots SandboxRunner in behind the same interface.
The Runner is RESPONSIBLE FOR and ONLY FOR:
- verifying the executable SHA-256 equals the descriptor pin BEFORE exec (verify-then-exec, design §4.6 — mirrors framework/harness/mcpclient/client.go:83-91);
- applying baseline hygiene (empty-by-default env allowlist, no fds beyond stdio, cwd = per-module scratch dir, own process group);
- wiring the child's stdin/stdout to a moduleproto.Codec;
- draining the child's stderr to a bounded moduleproto.RingSink.
It does NOT perform the handshake, poll ready, or apply capability policy — those live in the supervisor above. The returned RunningChild is at "process started, codec wired, peer NOT yet started": the supervisor installs the reverse broker handlers and then calls Peer.Start.
func SelectRunner ¶ added in v0.31.0
func SelectRunner(tier TrustTier, trusted Runner, sandbox *SandboxRunner) (Runner, error)
SelectRunner maps a descriptor's TrustTier to the Runner the supervisor spawns under (design §6 decision C — the inversion of the wave-2a site that unconditionally errored on TrustUntrusted):
- TrustTrusted → trusted (TrustedProcessRunner).
- TrustUntrusted + sandbox that Conforms() → sandbox (SandboxRunner).
- TrustUntrusted + nil/non-conforming sandbox → ErrSandboxUnavailable (the caller — supervisor.Register — wraps this as UntrustedNoSandboxError and the module never reaches Ready).
This is the single point that decides runner selection; the supervisor calls it once at Register and stores the result on the moduleSlot.
type RunningChild ¶ added in v0.31.0
type RunningChild interface {
// Codec is the protocol pipe (child's stdout read side + stdin write
// side wrapped in a moduleproto.Codec). The supervisor builds its
// [moduleproto.Peer] over this.
Codec() *moduleproto.Codec
// Stderr is the bounded sink draining the child's stderr. Use Tail()
// to read recent log output for diagnostics / Failed-state reports.
Stderr() *moduleproto.RingSink
// Pid returns the child's process id, or -1 if the process has exited
// and been reaped.
Pid() int
// ProcessGroup returns the child's Unix process group id (equal to
// the child's pid under Setpgid:true), or 0 on Windows / pre-spawn.
ProcessGroup() int
// CloseStdin closes the write side of the child's stdin so the child
// observes EOF. Idempotent. This is step 1 of the §4.6 teardown.
CloseStdin() error
// Kill sends SIGKILL to the child (and its process group on Unix).
// Idempotent. This is step 3 of the §4.6 teardown, used after the
// drain deadline expires. It does NOT block; call Wait to reap.
Kill() error
// Wait blocks until the child exits and returns the wait error
// (nil for a clean exit). It is the reap step. Calling Wait before
// Kill/CloseStdin is legal — it just blocks until natural exit.
Wait() error
}
RunningChild is the supervisor's handle to a spawned child process. The lifetime contract is design §4.6's lift list: CloseStdin → wait deadline → Kill → Wait.
type SQLModuleStore ¶ added in v0.17.0
type SQLModuleStore struct {
// contains filtered or unexported fields
}
SQLModuleStore persists module state in a gofastr_modules table. Self-migrating (CREATE TABLE IF NOT EXISTS) — not a migrate group.
func NewSQLModuleStore ¶ added in v0.17.0
func NewSQLModuleStore(db *sql.DB) (*SQLModuleStore, error)
NewSQLModuleStore creates a SQL-backed store and ensures the table exists. dialect is probed via migrate.DetectDialect when not explicit.
func (*SQLModuleStore) SetEnabled ¶ added in v0.17.0
SetEnabled upserts a module's enabled state.
type SQLProcessModuleStore ¶ added in v0.31.0
type SQLProcessModuleStore struct {
// contains filtered or unexported fields
}
SQLProcessModuleStore is the SQL-backed ProcessModuleStore. It owns no state beyond the *sql.DB; concurrent supervisors (one per replica) share the same store.
func NewSQLProcessModuleStore ¶ added in v0.31.0
func NewSQLProcessModuleStore(db *sql.DB) (*SQLProcessModuleStore, error)
NewSQLProcessModuleStore constructs a SQL-backed store over db. EnsureSchema is NOT called here; callers (the app wiring) call it explicitly at boot so the schema-creation error surfaces where the operator expects it.
func (*SQLProcessModuleStore) BumpGeneration ¶ added in v0.31.0
BumpGeneration atomically increments desired_generation and returns the new value.
func (*SQLProcessModuleStore) DeleteHeartbeat ¶ added in v0.31.0
func (s *SQLProcessModuleStore) DeleteHeartbeat(ctx context.Context, module, replicaID string) error
DeleteHeartbeat removes a replica's heartbeat row.
func (*SQLProcessModuleStore) Dialect ¶ added in v0.31.0
func (s *SQLProcessModuleStore) Dialect() migrate.Dialect
Dialect reports the detected dialect (debug / introspection).
func (*SQLProcessModuleStore) EnsureSchema ¶ added in v0.31.0
func (s *SQLProcessModuleStore) EnsureSchema(ctx context.Context) error
EnsureSchema creates the desired-state and heartbeat tables idempotently. DDL is dialect-aware (Postgres vs SQLite). The schema is intentionally narrow: no module-owned DDL touches this store (design §7 — host coordination state, NOT module bookkeeping).
func (*SQLProcessModuleStore) GetDesired ¶ added in v0.31.0
func (s *SQLProcessModuleStore) GetDesired(ctx context.Context, module string) (DesiredState, error)
GetDesired returns the desired-state row, or ErrNoDesiredRow if absent.
func (*SQLProcessModuleStore) Install ¶ added in v0.31.0
func (s *SQLProcessModuleStore) Install(ctx context.Context, d DesiredState) error
Install writes a new desired-state row. Returns ErrModuleInstalled if a row already exists for the module.
func (*SQLProcessModuleStore) ListDesired ¶ added in v0.31.0
func (s *SQLProcessModuleStore) ListDesired(ctx context.Context) ([]DesiredState, error)
ListDesired returns every desired-state row, ordered by module name.
func (*SQLProcessModuleStore) LiveReplicas ¶ added in v0.31.0
func (s *SQLProcessModuleStore) LiveReplicas(ctx context.Context, module string, ttl time.Duration) ([]ReplicaHeartbeat, error)
LiveReplicas returns heartbeat rows whose updated_at is within now-ttl..now.
func (*SQLProcessModuleStore) RecordHeartbeat ¶ added in v0.31.0
func (s *SQLProcessModuleStore) RecordHeartbeat(ctx context.Context, module, replicaID string, observedGen uint64, phase string) error
RecordHeartbeat upserts a replica heartbeat row.
func (*SQLProcessModuleStore) SetEffectiveGrants ¶ added in v0.31.0
func (s *SQLProcessModuleStore) SetEffectiveGrants(ctx context.Context, module string, grants []access.Permission) (uint64, error)
SetEffectiveGrants replaces the effective grant JSON AND bumps generation.
func (*SQLProcessModuleStore) SetEnabled ¶ added in v0.31.0
SetEnabled toggles enabled without bumping generation.
func (*SQLProcessModuleStore) SetMigrationsAppliedAt ¶ added in v0.31.0
func (s *SQLProcessModuleStore) SetMigrationsAppliedAt(ctx context.Context, module string, at *time.Time) error
SetMigrationsAppliedAt records (or clears) the migrations-applied timestamp.
type SandboxBackend ¶ added in v0.31.0
type SandboxBackend interface {
// Name is the backend identifier ("bwrap", "sandbox-exec",
// "appcontainer", "stub"). Stable across runs; used in logs and the
// ConformanceReport.
Name() string
// Available reports whether this backend can run on the current host:
// wrapper tool on PATH, kernel features present, sufficient privilege.
// When false, MissingReason explains why and Wrap returns an error.
Available() bool
// MissingReason is a short human-readable note explaining why an
// unavailable backend is unavailable. Empty when Available.
MissingReason() string
// Wrap rewrites cmd in place to execute the child under this backend's
// confinement. The caller has already applied baseline hygiene
// (scrubbed env, cwd=scratch, own process group); Wrap adds the
// OS-enforced denials on top. Returns an error iff the backend cannot
// wrap on this host (tool invocation failure, profile generation
// failure) — the caller treats that as fail-closed, never as "run
// anyway without confinement".
Wrap(cmd *exec.Cmd, opts SandboxOpts) error
// DeclaredProbes returns the probes this backend DECLARES it can
// enforce on this host when Available. The conformance runner still
// executes each probe to verify the declaration holds — this list
// drives the "what we aim to enforce" used for skip messaging when
// the backend is unavailable. It is the honest ceiling, not a claim:
// if a probe in this list later fails its run, the report records
// ProbeStatusFail and the backend does not conform.
DeclaredProbes() []ProbeID
}
SandboxBackend is the per-OS confinement wrapper (design §6 — v1 is a wrapper command, mirroring framework/harness/tool/builtins/bash.go's SandboxFn but with the fail-closed seam INVERTED: a nil/nil-error Wrap is mandatory for untrusted modules, and the constructor refuses to build a non-conforming runner). Each build-tagged implementation wraps the child exec in the OS confinement tool and declares which probes it can enforce on this host.
The interface is portable; the implementations are not. Go supervises processes portably but cannot confine them portably — stdlib syscall.SysProcAttr exposes Linux namespace/uid-mapping fields but the Darwin and Windows structs lack them entirely, hence the build-tagged split. The split boundary is linux vs not-linux (finer than the repo's existing unix-vs-not-unix split in processmodule_sysproc_*.go) because Darwin IS unix yet lacks namespace fields.
func HostSandboxBackend ¶ added in v0.31.0
func HostSandboxBackend() SandboxBackend
HostSandboxBackend returns this host's default sandbox backend, or nil if no backend is compiled in / available. It is the entry point NewSandboxRunner and TestSandboxConformance use to pick up the per-OS implementation without callers having to know the build tag.
The per-OS files (processmodule_sandbox_linux.go etc.) provide defaultSandboxBackend(); this wrapper exists so a host with no backend compiled in (none currently — every build has exactly one) returns nil rather than panicking, and so tests can inject a fake.
type SandboxOpts ¶ added in v0.31.0
type SandboxOpts struct {
// ScratchDir is the per-module writable working directory. Backends
// confine writes here (bwrap --bind, sandbox-exec file-write* subpath,
// Job Object inherited-handle restrictions).
ScratchDir string
// PackageDir is the read-only directory holding the module's artifact
// and its static assets. Backends mount/read it read-only (bwrap
// --ro-bind, sandbox-exec file-read* subpath).
PackageDir string
// ReadOnlyDirs are additional host paths the child may read but not
// write (system libraries, locale data). Backends mount them read-only.
ReadOnlyDirs []string
// PidLimit caps the child's process count (P6). 0 = backend default.
PidLimit int
// MemoryLimitBytes caps the child's resident memory (P6). 0 = default.
MemoryLimitBytes int64
// CpuQuotaPermil is the CPU quota in per-mille (1000 = one full CPU).
// 0 = backend default.
CpuQuotaPermil int
// FdLimit caps the child's open file descriptors (P6). 0 = default.
FdLimit int
}
SandboxOpts carries the per-module confinement parameters a backend applies when wrapping the child exec. Every field is host-derived from the operator-approved descriptor — the child never supplies these.
type SandboxRunner ¶ added in v0.31.0
type SandboxRunner struct {
// EnvAllowlist is the base env-var names the child receives (PATH,
// HOME, …). If nil, [DefaultChildEnvAllowlist] is used. This is the
// SAME baseline-hygiene allowlist TrustedProcessRunner applies — the
// two runners share it so P2 (no inherited secret) cannot drift.
EnvAllowlist []string
// NewCodec constructs a Codec over the supplied reader/writer. If nil,
// [moduleproto.NewCodec] is used with the spec's negotiated frame
// bytes. Mirrors TrustedProcessRunner.NewCodec for test injection.
NewCodec func(r io.Reader, w io.Writer, maxFrameBytes int) (*moduleproto.Codec, error)
// contains filtered or unexported fields
}
SandboxRunner is the wave-3a Runner for TrustUntrusted modules: baseline hygiene (shared with TrustedProcessRunner) PLUS the OS-enforced denials the selected SandboxBackend applies via Wrap. It is the only Runner an untrusted descriptor may run under; selection is fail-closed (a non-conforming backend ⇒ the constructor errors ⇒ the supervisor refuses Register, never a silent TrustedProcessRunner downgrade).
func NewSandboxRunner ¶ added in v0.31.0
func NewSandboxRunner(backend SandboxBackend, opts SandboxOpts) (*SandboxRunner, error)
NewSandboxRunner constructs a SandboxRunner over backend, PROBING the backend at construction per design §6. Returns a non-nil error iff the backend is unavailable or any probe P1–P7 fails or is unreachable — the caller (supervisor wiring) treats that as "no sandbox on this host" and fail-closes untrusted Register attempts.
The probe pass runs the full suite once and caches the report on the returned runner; subsequent Start calls do NOT re-probe (the backend's enforcement capability is a property of the host, not per-spawn).
func (*SandboxRunner) Backend ¶ added in v0.31.0
func (r *SandboxRunner) Backend() SandboxBackend
Backend returns the selected backend (for diagnostics / introspection).
func (*SandboxRunner) Conforms ¶ added in v0.31.0
func (r *SandboxRunner) Conforms() bool
Conforms reports whether the backend passed every probe P1–P7 at construction. The supervisor consults this at Register to fail-closed an untrusted module whose host lost conformance (e.g. the operator pointed NewSandboxRunner at a backend that was available at boot but is now not — belt-and-suspenders alongside the constructor's gate).
func (*SandboxRunner) Report ¶ added in v0.31.0
func (r *SandboxRunner) Report() ConformanceReport
Report returns the cached conformance report from construction. Operator-facing surfaces (introspection, install UI) read this to show WHY an untrusted module fail-closed on this host.
func (*SandboxRunner) Start ¶ added in v0.31.0
func (r *SandboxRunner) Start(ctx context.Context, spec ChildSpec) (RunningChild, error)
Start implements Runner.Start. It is the untrusted-tier spawn: baseline hygiene (shared [prepareChildForSpawn]) + backend.Wrap (the OS-enforced denials) + shared [startPreparedChild]. The backend.Wrap step is the ONLY difference from TrustedProcessRunner.Start — that is the design's point: the two runners are identical except for the OS-enforced denial layer.
type SchemaChange ¶
type SchemaChange = migrate.SchemaChange
type SchemaSnapshot ¶
type SchemaSnapshot = migrate.SchemaSnapshot
type SetupRunner ¶ added in v0.17.0
type SetupRunner interface {
// Incomplete reports whether first-run setup has not yet finished.
Incomplete(ctx context.Context) (bool, error)
// CanRunHeadless reports whether every required field across all
// steps resolves from the environment, so bootstrap can run inline.
CanRunHeadless(ctx context.Context) (bool, error)
// RunSteps runs all steps synchronously. Called only when
// CanRunHeadless returns true. An error aborts Start.
RunSteps(ctx context.Context) error
// Handler returns the interactive setup surface. swap is called
// once setup completes to switch to the real handler. healthz and
// readyz are the app's existing health handlers, passed so the
// setup surface can serve /healthz and /readyz during setup.
Handler(swap func(), healthz, readyz http.HandlerFunc) http.Handler
// SetupURL returns the operator-facing URL (with token) for the
// startup banner. addr is the bound listen address. Returns ""
// when not applicable (headless path or token disabled).
SetupURL(addr string) string
}
SetupRunner is the interface a first-run setup implementation provides. framework defines it (rather than importing battery/setup) to avoid a layering cycle: battery/setup implements it and the host wires the concrete runner via WithSetup.
Lifecycle inside App.Start:
- Incomplete is consulted after plugin init and before consumer start. When true (and GOFASTR_SETUP != "off"), Start enters setup mode.
- CanRunHeadless distinguishes the two skins. When true every required field resolves from the environment and RunSteps executes inline before the port binds — no wizard is ever served.
- When CanRunHeadless is false the interactive skin is served: Handler returns the wizard surface and swap is invoked once the final step succeeds, atomically switching to the real app handler and starting deferred background consumers.
type StringColumn ¶
type StringColumn = entity.StringColumn
type SupervisorConfig ¶ added in v0.31.0
type SupervisorConfig struct {
// Store is the durable coordination substrate. Required.
Store ProcessModuleStore
// Runner spawns children for TrustTrusted modules. Defaults to
// [TrustedProcessRunner] when nil (tests usually inject a fake).
Runner Runner
// Sandbox spawns TrustUntrusted modules. Nil is the fail-closed
// state: every TrustUntrusted descriptor fails Register with
// [UntrustedNoSandboxError] (design §6 — never a silent downgrade
// to Runner). A non-nil SandboxRunner whose backend does not pass
// the P1–P7 conformance probe is equivalent to nil — SelectRunner
// consults Sandbox.Conforms() at Register.
Sandbox *SandboxRunner
// Broker is the reverse-request capability broker. Defaults to
// [NopBroker] (deny-all) when nil — the real broker lands in a later
// wave.
Broker ReverseBroker
// ToolRegistrar installs a module's MCP tool surface into the host
// *mcp.Server after handshake verifies it (design §5.1 / §4.7 step 5).
// Nil means the supervisor still verifies digests at handshake (a
// mismatch quarantines the module) but does not register tools into
// any MCP server — tests that do not exercise the tool surface leave
// it nil. The host wiring sets it to a [ModuleToolRegistry].
ToolRegistrar ToolRegistrar
// ToolListTimeout bounds the module.tool.list round-trip at handshake.
// Default 2s (sub-budget of SpawnDeadline). The deadline on the call
// is min(remaining spawn budget, this).
ToolListTimeout time.Duration
// ReplicaID identifies this supervisor in heartbeat rows. Defaults to
// a random hex string when empty.
ReplicaID string
// SpawnDeadline bounds the spawn sequence (spawn + handshake + ready
// poll). Default 5s (design §8).
SpawnDeadline time.Duration
// PollInterval is the periodic reconcile tick (cross-replica
// generation convergence upper bound). Default 2s (design §8 ~2s
// target).
PollInterval time.Duration
// HeartbeatInterval is how often the supervisor writes a heartbeat
// row for each Ready module. Default 1s.
HeartbeatInterval time.Duration
// LeaseTTL is the fail-closed bound: if the store cannot be re-read
// for this long, children are drained and routes 503/404. Default 5s
// (≈ 3× HeartbeatInterval, design §8). This INVERTS in-process
// handleRemoteToggle's fail-open — documented at
// [SQLProcessModuleStore].
LeaseTTL time.Duration
// DrainPerModule bounds a single child's graceful drain. The shared
// shutdown budget is min(DrainPerModule, remaining shared budget).
// Default 30s (design §8).
DrainPerModule time.Duration
// BackoffMin / BackoffMax bound the restart backoff (design §8:
// 250ms → 30s + jitter).
BackoffMin time.Duration
BackoffMax time.Duration
// CircuitThreshold is the crash count in CircuitWindow that opens the
// circuit (design §8: 5). When open, the supervisor stops restarting
// until a generation bump resets it.
CircuitThreshold int
// CircuitWindow is the rolling window for crash counts (design §8:
// 60s).
CircuitWindow time.Duration
// Now is the clock the supervisor reads. Defaults to [time.Now] when
// nil — tests inject a tunable clock so the 60s circuit window does
// not need real sleeps.
Now func() time.Time
// Logf receives diagnostic lines (state transitions, crash
// classifications). Optional; nil = silent.
Logf func(format string, args ...any)
}
SupervisorConfig tunes the supervisor. Every field has a v1 default; tests override the durations and the clock to avoid real sleeps and to compress the 60s circuit window.
type TenantConfig ¶
type TenantConfig = tenant.TenantConfig
type TestApp ¶
type TestApp struct {
App *App
// contains filtered or unexported fields
}
TestApp wraps an App for in-memory testing (no real HTTP listener). Uses httptest.NewRequest + http.Handler.ServeHTTP for speed.
func TestHarness ¶
TestHarness creates an in-memory test harness around an App. No real HTTP server is started — requests go directly through the router.
Calls app.InitPlugins() internally so plugin / battery wiring is in place before the first request. Without this, RegisterPlugin'd behaviour silently does nothing under the harness (Init never fires). Idempotent guard inside InitPlugins makes this safe even if Start is called later by the same test.
func (*TestApp) AsUser ¶ added in v0.29.0
AsUser returns a copy of the harness whose Get/Post/Put/Delete/Request calls carry an authenticated caller in context (core/handler.SetUser). Needed because auto-CRUD is secure-by-default: an entity declaring neither OwnerField nor Access nor Public requires a session for every operation (see EntityConfig.Public, framework/docs/content/security.md "Default CRUD authentication"). user can be any non-nil value — a bare struct{ ID string }{ID: "u1"} is enough to pass the baseline gate; use a real user type when the test also needs battery/auth's owner extractor or an access.Policy to resolve roles from it.
ta := TestHarness(t, app).AsUser(struct{ ID string }{ID: "u1"})
ta.Post("/posts", body).AssertStatus(t, http.StatusCreated)
func (*TestApp) Close ¶
func (ta *TestApp) Close()
Close is a no-op for in-memory testing (provided for API consistency).
func (*TestApp) Delete ¶
func (ta *TestApp) Delete(path string) *TestResponse
Delete performs a DELETE request.
func (*TestApp) Get ¶
func (ta *TestApp) Get(path string) *TestResponse
Get performs a GET request and returns a TestResponse for assertions.
func (*TestApp) Post ¶
func (ta *TestApp) Post(path string, body any) *TestResponse
Post performs a POST request with a JSON body.
type TestRequest ¶
type TestRequest struct {
// contains filtered or unexported fields
}
TestRequest wraps an http.Request with a fluent builder API.
func (*TestRequest) Execute ¶
func (tr *TestRequest) Execute() *TestResponse
Execute sends the request and returns a TestResponse.
func (*TestRequest) WithBody ¶
func (tr *TestRequest) WithBody(body any) *TestRequest
WithBody sets the request body. Non-reader values are marshalled as JSON.
func (*TestRequest) WithHeader ¶
func (tr *TestRequest) WithHeader(key, value string) *TestRequest
WithHeader adds a header to the request.
type TestResponse ¶
type TestResponse struct {
// contains filtered or unexported fields
}
TestResponse wraps the recorded response with assertion helpers.
func (*TestResponse) AssertBodyContains ¶
func (tr *TestResponse) AssertBodyContains(t testing.TB, substr string) *TestResponse
AssertBodyContains asserts the body contains the given substring.
func (*TestResponse) AssertHeader ¶
func (tr *TestResponse) AssertHeader(t testing.TB, key, expected string) *TestResponse
AssertHeader asserts a response header value. Chainable.
func (*TestResponse) AssertJSON ¶
func (tr *TestResponse) AssertJSON(t testing.TB, expected any) *TestResponse
AssertJSON asserts the response body equals expected after JSON normalisation. Compares decoded values (not raw strings) so key order and number types don't matter.
func (*TestResponse) AssertStatus ¶
func (tr *TestResponse) AssertStatus(t testing.TB, expected int) *TestResponse
AssertStatus asserts the HTTP status code. Chainable.
func (*TestResponse) Body ¶
func (tr *TestResponse) Body() string
Body returns the response body as a string.
func (*TestResponse) JSON ¶
func (tr *TestResponse) JSON(v any) error
JSON decodes the response body into v.
func (*TestResponse) Status ¶
func (tr *TestResponse) Status() int
Status returns the HTTP status code.
type TimestampColumn ¶
type TimestampColumn = entity.TimestampColumn
type ToolDigest ¶ added in v0.31.0
type ToolDigest struct {
// ID is the tool's descriptor-local identifier, namespaced under
// "module.<name>." when registered into the host's MCP server.
ID string
// SHA256 is the hex-encoded SHA-256 of the tool's canonical JSON
// (id+name+description+input_schema). Mismatch with module.tool.list
// quarantines the module (terminal Failed this wave).
SHA256 string
}
ToolDigest is the digest of one MCP tool the module may expose (design §5.1, optional surface). The host registers tools from the descriptor and byte-compares against module.tool.list at handshake; the child cannot add, rename, or reshape at runtime.
type ToolRegistrar ¶ added in v0.31.0
type ToolRegistrar interface {
RegisterTools(moduleName string, tools []moduleproto.Tool) error
UnregisterTools(moduleName string) error
}
ToolRegistrar is the supervisor's hook into the host MCP server: the supervisor verifies the tool surface at handshake (design §4.7 step 5) and hands the verified moduleproto.Tool slice to RegisterTools, which installs each under its namespaced id. The implementation is idempotent across respawns: the MCP handler it stamps in looks up the CURRENT live peer at call time, so a restarted child needs no re-registration. UnregisterTools is kept for symmetry; v1 leaves the tool registered and gates visibility through the call gate (core/mcp has no tool-removal API), so it is a tracking-only no-op documented as such.
type TrustTier ¶ added in v0.31.0
type TrustTier int
TrustTier selects the runner a process module is launched under (design §6, decision C). It comes from the operator-approved descriptor, never a child self-claim.
const ( // TrustUntrusted requires a probe-passing SandboxRunner (design §6 // decision C). At Register, [SelectRunner] maps it to the // supervisor's configured *SandboxRunner iff that runner's backend // passes the P1–P7 conformance probe on this host; otherwise // Register fails with [UntrustedNoSandboxError] and the module // never reaches Ready (fail-closed — never a silent downgrade to // TrustedProcessRunner). TrustUntrusted TrustTier = iota // TrustTrusted runs under TrustedProcessRunner: crash isolation only, // baseline hygiene. First-party / dev; never auto-selected for // untrusted. TrustTrusted )
type TrustedProcessRunner ¶ added in v0.31.0
type TrustedProcessRunner struct {
// EnvAllowlist is the base env-var names the child receives (PATH,
// HOME, …). If nil, [DefaultChildEnvAllowlist] is used. Names not in
// the union of this list + spec.InheritEnv are dropped.
EnvAllowlist []string
// NewCodec constructs a Codec over the supplied reader/writer. If nil,
// [moduleproto.NewCodec] is used with the spec's negotiated frame
// bytes (clamped to DefaultMaxFrameBytes). Tests inject a fake to
// avoid touching real stdio.
NewCodec func(r io.Reader, w io.Writer, maxFrameBytes int) (*moduleproto.Codec, error)
}
TrustedProcessRunner is the wave-2a Runner: crash isolation only (design §6 decision C — "TrustedProcessRunner for dev"). It applies baseline hygiene (empty-by-default env allowlist, own process group, cwd = per-module scratch dir, SHA-256 pin verified before exec, stderr → RingSink) via the shared [prepareChildForSpawn]/[startPreparedChild] helpers — the SAME helpers SandboxRunner uses, so the two runners cannot drift on baseline hygiene (design §6: "applied by BOTH runners"). It is NOT a security sandbox — an unconfined child can still open/connect/dial. Untrusted modules must use SandboxRunner, which layers OS-enforced denials on top of the same baseline.
func (*TrustedProcessRunner) Start ¶ added in v0.31.0
func (r *TrustedProcessRunner) Start(ctx context.Context, spec ChildSpec) (RunningChild, error)
Start implements Runner.Start. It is the trusted-tier spawn: verify the SHA pin, build the exec.Cmd with baseline hygiene, wire stdio, exec. It is a thin composition of [prepareChildForSpawn] (verify + cmd + pipes) and [startPreparedChild] (exec + codec + stderr drain); SandboxRunner reuses both with a backend.Wrap step in between.
type TypedQuery ¶
type TypedQuery[T any] = crud.TypedQuery[T]
type UCPConfig ¶ added in v0.10.0
type UCPConfig struct {
ProtocolVersion string
Services []map[string]any
Capabilities []map[string]any
Endpoints []map[string]any
// SpecURLs are advertised spec/schema URLs (the scanner expects them reachable).
SpecURLs []string
}
UCPConfig configures /.well-known/ucp (ucp.dev).
type UUIDColumn ¶
type UUIDColumn = entity.UUIDColumn
type UnknownCapabilityError ¶ added in v0.30.0
type UnknownCapabilityError = access.UnknownCapabilityError
UnknownCapabilityError is a strict-mode Grant rejection; errors.As on it to answer 400 (caller typo) instead of 500 (store failure).
type UntrustedNoSandboxError ¶ added in v0.31.0
type UntrustedNoSandboxError struct {
Module string
// contains filtered or unexported fields
}
UntrustedNoSandboxError is returned by Register when a descriptor with TrustUntrusted is registered and no probe-passing sandbox backend is available on this host (design §6 fail-closed). The module stays un-Registered and never reaches Ready. The wrapped [cause] explains why (no backend configured, backend unavailable, or probe failure) and is exposed via errors.Unwrap so callers can errors.Is against ErrSandboxUnavailable.
func (*UntrustedNoSandboxError) Error ¶ added in v0.31.0
func (e *UntrustedNoSandboxError) Error() string
func (*UntrustedNoSandboxError) Unwrap ¶ added in v0.31.0
func (e *UntrustedNoSandboxError) Unwrap() error
Unwrap allows errors.Is(err, ErrSandboxUnavailable) and errors.As on the wrapped probe/construction failure.
type ValidationError ¶ added in v0.18.0
type ValidationError = crud.ValidationError
type ValidationRegistry ¶
type ValidationRegistry = entity.ValidationRegistry
type ValidatorFunc ¶
type ValidatorFunc = entity.ValidatorFunc
type WebBotAuthConfig ¶ added in v0.10.0
type WebBotAuthConfig struct {
// Keys is the JWK Set "keys" array — the site's public signing keys.
Keys []map[string]any
}
WebBotAuthConfig configures /.well-known/http-message-signatures-directory. The site publishes its signing keys (a JWK Set) so receivers can verify the requests it sends as a bot/agent. (This is the publishing side, not RFC 9421 inbound verification.)
Source Files
¶
- agent_extras.go
- agents.go
- app.go
- audit.go
- authmd.go
- battery.go
- dbctx.go
- doc.go
- export.go
- export_data.go
- flags.go
- health.go
- i18n.go
- mcp_app.go
- mcp_control.go
- mcp_introspection.go
- module.go
- oauth_resource.go
- plugin.go
- processmodule.go
- processmodule_app.go
- processmodule_broker.go
- processmodule_broker_iface.go
- processmodule_migrate.go
- processmodule_probe.go
- processmodule_probe_unix.go
- processmodule_proxy.go
- processmodule_runner.go
- processmodule_sandbox.go
- processmodule_sandbox_linux.go
- processmodule_store.go
- processmodule_supervisor.go
- processmodule_sysproc_unix.go
- processmodule_tools.go
- reexports_access.go
- reexports_cron.go
- reexports_crud.go
- reexports_entity.go
- reexports_event.go
- reexports_hook.go
- reexports_migrate.go
- reexports_observability.go
- reexports_openapi.go
- reexports_routegroup.go
- reexports_tenant.go
- registry.go
- role.go
- secret.go
- seed.go
- setup.go
- testharness.go
- tx.go
- typed_hooks.go
- wellknown.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package agentsinv is a process-wide registry of agent-onboarding snippets contributed by batteries and the framework root.
|
Package agentsinv is a process-wide registry of agent-onboarding snippets contributed by batteries and the framework root. |
|
Package datexport is a process-wide registry of data-bearing tables that live OUTSIDE the framework entity registry — the physical tables a battery (auth sessions, the job queue, …) or an app creates with raw DDL.
|
Package datexport is a process-wide registry of data-bearing tables that live OUTSIDE the framework entity registry — the physical tables a battery (auth sessions, the job queue, …) or an app creates with raw DDL. |
|
Package db holds shared low-level database abstractions used across the GoFastr framework subpackages.
|
Package db holds shared low-level database abstractions used across the GoFastr framework subpackages. |
|
Package dev provides dev-mode-only helpers (livereload, debug surfaces).
|
Package dev provides dev-mode-only helpers (livereload, debug surfaces). |
|
Package docs ships the framework's user-facing markdown docs as an embedded filesystem.
|
Package docs ships the framework's user-facing markdown docs as an embedded filesystem. |
|
experimental
|
|
|
apiversions
Package apiversions provides first-class API versioning built on top of route groups.
|
Package apiversions provides first-class API versioning built on top of route groups. |
|
Package factory provides Rails-style fixture / factory helpers for GoFastr tests and dev-time seeders.
|
Package factory provides Rails-style fixture / factory helpers for GoFastr tests and dev-time seeders. |
|
Package fanout provides a Postgres-backed implementation of core/fanout.Fanout using LISTEN/NOTIFY.
|
Package fanout provides a Postgres-backed implementation of core/fanout.Fanout using LISTEN/NOTIFY. |
|
Package harness is part of the GoFastr harness.
|
Package harness is part of the GoFastr harness. |
|
client
Package client is part of the GoFastr harness.
|
Package client is part of the GoFastr harness. |
|
client/tui
Package tui is part of the GoFastr harness.
|
Package tui is part of the GoFastr harness. |
|
client/web
Package web is part of the GoFastr harness.
|
Package web is part of the GoFastr harness. |
|
context
Package context is part of the GoFastr harness.
|
Package context is part of the GoFastr harness. |
|
control
Package control is part of the GoFastr harness.
|
Package control is part of the GoFastr harness. |
|
control/auth
Package auth implements the capability-token model: claim set, internal JWT-like encoding (no third-party dep), revocation list, and the issuance flow with TTY/notification confirmation.
|
Package auth implements the capability-token model: claim set, internal JWT-like encoding (no third-party dep), revocation list, and the issuance flow with TTY/notification confirmation. |
|
control/conformance
Package conformance is the cross-transport parity test framework.
|
Package conformance is the cross-transport parity test framework. |
|
control/inproc
Package inproc is part of the GoFastr harness.
|
Package inproc is part of the GoFastr harness. |
|
control/mcpserver
Package mcpserver will expose the harness engine as an MCP server.
|
Package mcpserver will expose the harness engine as an MCP server. |
|
control/multiplex
Package multiplex is part of the GoFastr harness.
|
Package multiplex is part of the GoFastr harness. |
|
control/resources
Package resources is part of the GoFastr harness.
|
Package resources is part of the GoFastr harness. |
|
control/rest
Package rest is part of the GoFastr harness.
|
Package rest is part of the GoFastr harness. |
|
control/ws
Package ws will implement the WebSocket transport for the control plane.
|
Package ws will implement the WebSocket transport for the control plane. |
|
engine
Package engine is part of the GoFastr harness.
|
Package engine is part of the GoFastr harness. |
|
hook
Package hook is part of the GoFastr harness.
|
Package hook is part of the GoFastr harness. |
|
ids
Package ids is part of the GoFastr harness.
|
Package ids is part of the GoFastr harness. |
|
internal/clock
Package clock provides a swap-able clock for tests.
|
Package clock provides a swap-able clock for tests. |
|
internal/ulid
Package ulid is part of the GoFastr harness.
|
Package ulid is part of the GoFastr harness. |
|
logging
Package logging is part of the GoFastr harness.
|
Package logging is part of the GoFastr harness. |
|
mcpclient
Package mcpclient implements the MCP client (consumer side) the harness uses to talk to external MCP servers.
|
Package mcpclient implements the MCP client (consumer side) the harness uses to talk to external MCP servers. |
|
memory
Package memory is part of the GoFastr harness.
|
Package memory is part of the GoFastr harness. |
|
plugin
Package plugin is part of the GoFastr harness.
|
Package plugin is part of the GoFastr harness. |
|
profile
Package profile is part of the GoFastr harness.
|
Package profile is part of the GoFastr harness. |
|
provider
Package provider is part of the GoFastr harness.
|
Package provider is part of the GoFastr harness. |
|
provider/copilot
Package copilot implements the GitHub Copilot Provider.
|
Package copilot implements the GitHub Copilot Provider. |
|
provider/credstore
Package credstore implements credential storage.
|
Package credstore implements credential storage. |
|
provider/failover
Package failover composes a chain of Providers with a circuit-breaker per upstream.
|
Package failover composes a chain of Providers with a circuit-breaker per upstream. |
|
provider/helper
Package helper is part of the GoFastr harness.
|
Package helper is part of the GoFastr harness. |
|
provider/internal/openai
Package openai is an internal OpenAI-compatible adapter used by the OpenRouter and ZAI providers (both speak the same wire shape).
|
Package openai is an internal OpenAI-compatible adapter used by the OpenRouter and ZAI providers (both speak the same wire shape). |
|
provider/openrouter
Package openrouter is part of the GoFastr harness.
|
Package openrouter is part of the GoFastr harness. |
|
provider/routing
Package routing will implement RoutingProvider: a Provider that composes {router, executors[]} so a single turn can use a cheap model for routing and an expensive model for execution.
|
Package routing will implement RoutingProvider: a Provider that composes {router, executors[]} so a single turn can use a cheap model for routing and an expensive model for execution. |
|
provider/zai
Package zai is part of the GoFastr harness.
|
Package zai is part of the GoFastr harness. |
|
secrets
Package secrets locates and loads the repo-local .harness-secrets/env file.
|
Package secrets locates and loads the repo-local .harness-secrets/env file. |
|
session
Package session is part of the GoFastr harness.
|
Package session is part of the GoFastr harness. |
|
session/sqlite
Package sqlite is part of the GoFastr harness.
|
Package sqlite is part of the GoFastr harness. |
|
skill
Package skill is part of the GoFastr harness.
|
Package skill is part of the GoFastr harness. |
|
skill/skillmd
Package skillmd is part of the GoFastr harness.
|
Package skillmd is part of the GoFastr harness. |
|
slash
Package slash is part of the GoFastr harness.
|
Package slash is part of the GoFastr harness. |
|
tool
Package tool is part of the GoFastr harness.
|
Package tool is part of the GoFastr harness. |
|
tool/builtins
Package builtins is part of the GoFastr harness.
|
Package builtins is part of the GoFastr harness. |
|
tool/pack
Package pack is part of the GoFastr harness.
|
Package pack is part of the GoFastr harness. |
|
tool/permission
Package permission is part of the GoFastr harness.
|
Package permission is part of the GoFastr harness. |
|
tracing
Package tracing is part of the GoFastr harness.
|
Package tracing is part of the GoFastr harness. |
|
Package i18nui provides translated default strings for framework UI surfaces.
|
Package i18nui provides translated default strings for framework UI surfaces. |
|
Package image is a chainable image pipeline: decode → transform → encode, pure Go with only the standard library and golang.org/x/image as dependencies.
|
Package image is a chainable image pipeline: decode → transform → encode, pure Go with only the standard library and golang.org/x/image as dependencies. |
|
internal/vp8l
Package vp8l implements a pure-Go VP8L (WebP lossless) encoder.
|
Package vp8l implements a pure-Go VP8L (WebP lossless) encoder. |
|
internal
|
|
|
casing
Package casing holds snake_case <-> camelCase helpers used internally by the GoFastr framework.
|
Package casing holds snake_case <-> camelCase helpers used internally by the GoFastr framework. |
|
testdb
Package testdb provides shared per-test database helpers used by the framework's internal tests AND by framework_test (external) tests that can't access package-private helpers.
|
Package testdb provides shared per-test database helpers used by the framework's internal tests AND by framework_test (external) tests that can't access package-private helpers. |
|
Package isolation resolves worktree-specific local runtime resources.
|
Package isolation resolves worktree-specific local runtime resources. |
|
Package lifecycle provides a documented, cooperative graceful-shutdown contract for GoFastr applications.
|
Package lifecycle provides a documented, cooperative graceful-shutdown contract for GoFastr applications. |
|
Package outbox implements a transactional outbox for reliable event delivery to declared durable consumers.
|
Package outbox implements a transactional outbox for reliable event delivery to declared durable consumers. |
|
Package owner provides a single seam for "who owns this row" lookups during CRUD operations.
|
Package owner provides a single seam for "who owns this row" lookups during CRUD operations. |
|
Package pluginhost is the reusable, plugin-agnostic host glue for GoFastr heavy-JS plugins that run inside an opaque-origin sandboxed iframe.
|
Package pluginhost is the reusable, plugin-agnostic host glue for GoFastr heavy-JS plugins that run inside an opaque-origin sandboxed iframe. |
|
Package routegroup provides the App-level route group abstraction.
|
Package routegroup provides the App-level route group abstraction. |
|
Package sdk is the shared contract between `gofastr generate sdk` (which emits client SDKs and packs the downloadable artifacts) and the serving side (framework/sdkdocs, which hosts them).
|
Package sdk is the shared contract between `gofastr generate sdk` (which emits client SDKs and packs the downloadable artifacts) and the serving side (framework/sdkdocs, which hosts them). |
|
Package sdkdocs serves a public SDK documentation site for a GoFastr app — install guides, a live per-entity API reference, auth and error guides — plus download routes for the pregenerated SDK artifacts that `gofastr generate sdk` emits (see framework/sdk for the shared contract).
|
Package sdkdocs serves a public SDK documentation site for a GoFastr app — install guides, a live per-entity API reference, auth and error guides — plus download routes for the pregenerated SDK artifacts that `gofastr generate sdk` emits (see framework/sdk for the shared contract). |
|
Package static implements static-site generation for a framework.App with a UIHost mounted on it.
|
Package static implements static-site generation for a framework.App with a UIHost mounted on it. |
|
Package testkit provides PUBLIC test helpers for host apps that use the GoFastr framework.
|
Package testkit provides PUBLIC test helpers for host apps that use the GoFastr framework. |
|
Package ui is the framework's opinionated component layer on top of core-ui.
|
Package ui is the framework's opinionated component layer on top of core-ui. |
|
theme
Package theme is the canonical home for the framework's visual design system.
|
Package theme is the canonical home for the framework's visual design system. |
|
Package uihost wires a core-ui application onto a framework.App's router.
|
Package uihost wires a core-ui application onto a framework.App's router. |
|
internal/sessiontoken
Package sessiontoken mints and verifies the stateless HMAC-signed tokens that replace the uihost's in-memory session map.
|
Package sessiontoken mints and verifies the stateless HMAC-signed tokens that replace the uihost's in-memory session map. |
|
uinoderender
Package uinoderender maps a validated ui.node.v1 tree (github.com/DonaldMurillo/gofastr/core-ui/uinodev1.Tree) to host-owned HTML by composing the framework's design-system primitives — github.com/DonaldMurillo/gofastr/framework/ui and github.com/DonaldMurillo/gofastr/core-ui/html.
|
Package uinoderender maps a validated ui.node.v1 tree (github.com/DonaldMurillo/gofastr/core-ui/uinodev1.Tree) to host-owned HTML by composing the framework's design-system primitives — github.com/DonaldMurillo/gofastr/framework/ui and github.com/DonaldMurillo/gofastr/core-ui/html. |