backend

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package backend wires the app layer to a concrete store. LocalBackend opens the embedded SQLite database and constructs every app with the right dependencies. The CLI, server, and MCP surface all call into the same backend — nothing above this package reaches into the store.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bootstrap

func Bootstrap(home string) (*sql.DB, *sqlite.Store, error)

Bootstrap opens the database at home without reading config.yaml. It runs migrations and returns a store the caller can use to seed state (e.g. `brainjar init` creating the default workspace). The caller is responsible for closing DB.

Types

type Backend

type Backend interface {
	io.Closer

	// WorkspaceID returns the workspace bound to this backend.
	WorkspaceID() models.WorkspaceID

	// Souls
	SoulsList(ctx context.Context) ([]models.Soul, error)
	SoulsGet(ctx context.Context, slug models.Slug) (*models.Soul, error)
	SoulsUpsert(ctx context.Context, slug models.Slug, content string) (*models.Soul, error)
	SoulsDelete(ctx context.Context, slug models.Slug) error

	// Personas
	PersonasList(ctx context.Context) ([]models.Persona, error)
	PersonasGet(ctx context.Context, slug models.Slug) (*models.Persona, error)
	PersonasUpsert(ctx context.Context, slug models.Slug, content string, bundledRules []models.Slug) (*models.Persona, error)
	PersonasDelete(ctx context.Context, slug models.Slug) error

	// Rules
	RulesList(ctx context.Context) ([]models.Rule, error)
	RulesGet(ctx context.Context, slug models.Slug) (*models.Rule, error)
	RulesUpsert(ctx context.Context, slug models.Slug, entries []models.RuleEntry) (*models.Rule, error)
	RulesDelete(ctx context.Context, slug models.Slug) error

	// Brains
	BrainsList(ctx context.Context) ([]models.Brain, error)
	BrainsGet(ctx context.Context, slug models.Slug) (*models.Brain, error)
	BrainsUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, prefs *models.ModelPrefs) (*models.Brain, error)
	BrainsDelete(ctx context.Context, slug models.Slug) error

	// State — projectSlug is empty from the CLI (workspace scope only);
	// the server still accepts it for handler parity.
	StateFindLayers(ctx context.Context, projectSlug string) ([]models.LayerOverride, error)
	StateResolveForScope(ctx context.Context, projectSlug string) (*models.EffectiveState, error)
	StateSet(ctx context.Context, scopeType models.ScopeType, referenceID string, partial *models.LayerOverride) error
	StateDelete(ctx context.Context, scopeType models.ScopeType, referenceID string) error

	// Compose
	ComposePrompt(ctx context.Context, req models.ComposeRequest) (*models.ComposeResponse, error)

	// Versions
	VersionsList(ctx context.Context, contentType models.ContentType, slug models.Slug) ([]models.VersionSummary, error)
	VersionsGet(ctx context.Context, contentType models.ContentType, slug models.Slug, version int) (*models.ContentVersion, error)

	// Workspaces — management endpoints take an explicit id because the
	// target may not be the caller's own workspace.
	WorkspaceCreate(ctx context.Context, name string) (*models.Workspace, error)
	WorkspaceList(ctx context.Context) ([]models.Workspace, error)
	WorkspaceGetByName(ctx context.Context, name string) (*models.Workspace, error)
	WorkspaceRename(ctx context.Context, id models.WorkspaceID, newName string) (*models.Workspace, error)
	WorkspaceDelete(ctx context.Context, id models.WorkspaceID) error
	WorkspacePurge(ctx context.Context, id models.WorkspaceID) error

	// API keys — scoped to the caller's workspace.
	APIKeyCreate(ctx context.Context, label string) (plaintext string, record *models.APIKey, err error)
	APIKeyList(ctx context.Context) ([]models.APIKeySummary, error)
	APIKeyRevoke(ctx context.Context, id models.APIKeyID) error

	// Admin — pack export/import.
	AdminExport(ctx context.Context) (*bundle.ContentBundle, error)
	AdminImport(ctx context.Context, cb *bundle.ContentBundle) (*bundle.ImportResult, error)
}

Backend is the contract every brainjar surface (CLI, MCP, future integrations) calls to read and mutate workspace content. LocalBackend satisfies it against SQLite; RemoteBackend satisfies it against the brainjar HTTP API.

The workspace is bound to the backend at construction time — local mode reads it from config, remote mode sends it as the X-Brainjar-Workspace header. Callers pass slugs and content, never workspace IDs. Workspace-management methods are the exception: they operate on arbitrary workspaces by ID, not the caller's own.

Errors are either *apperrors.Error or *apperrors.RefError — never bare strings or store errors — so the CLI error layer maps Code() uniformly across local and remote.

type LocalBackend

type LocalBackend struct {
	// contains filtered or unexported fields
}

LocalBackend is the SQLite-backed, single-workspace backend. App fields are private — callers go through the Backend interface (which the MCP tool layer also reads), never by reaching in directly.

func NewForTesting

func NewForTesting(ws models.WorkspaceID, db *sql.DB, store *sqlite.Store) *LocalBackend

NewForTesting wires a LocalBackend against an already-open database and store, bound to ws. Intended for tests that want a working Backend without going through config.Load / backend.Open. The caller owns db and is responsible for closing it — LocalBackend.Close is a no-op on the returned value.

func Open

func Open(home string) (*LocalBackend, error)

Open opens the SQLite database under home, runs pending migrations, loads the config to resolve the workspace ID, and wires every app. Returns an error if home has no config — call Bootstrap first.

func (*LocalBackend) APIKeyCreate

func (b *LocalBackend) APIKeyCreate(ctx context.Context, label string) (string, *models.APIKey, error)

func (*LocalBackend) APIKeyList

func (b *LocalBackend) APIKeyList(ctx context.Context) ([]models.APIKeySummary, error)

func (*LocalBackend) APIKeyRevoke

func (b *LocalBackend) APIKeyRevoke(ctx context.Context, id models.APIKeyID) error

func (*LocalBackend) AdminExport

func (b *LocalBackend) AdminExport(ctx context.Context) (*bundle.ContentBundle, error)

func (*LocalBackend) AdminImport

func (*LocalBackend) BrainsDelete

func (b *LocalBackend) BrainsDelete(ctx context.Context, slug models.Slug) error

func (*LocalBackend) BrainsGet

func (b *LocalBackend) BrainsGet(ctx context.Context, slug models.Slug) (*models.Brain, error)

func (*LocalBackend) BrainsList

func (b *LocalBackend) BrainsList(ctx context.Context) ([]models.Brain, error)

func (*LocalBackend) BrainsUpsert

func (b *LocalBackend) BrainsUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, prefs *models.ModelPrefs) (*models.Brain, error)

func (*LocalBackend) Close

func (b *LocalBackend) Close() error

Close closes the underlying database connection.

func (*LocalBackend) ComposePrompt

func (*LocalBackend) DB

func (b *LocalBackend) DB() *sql.DB

DB returns the underlying database handle. Exposed so `brainjar serve` can wire /readyz to ping it; all business logic still goes through the Backend interface.

func (*LocalBackend) KeyResolver

func (b *LocalBackend) KeyResolver() middleware.KeyResolver

KeyResolver returns a middleware.KeyResolver backed by the backend's APIKeyApp. serve.go passes it to middleware.Auth so bearer tokens resolve through the same app that created them.

func (*LocalBackend) PersonasDelete

func (b *LocalBackend) PersonasDelete(ctx context.Context, slug models.Slug) error

func (*LocalBackend) PersonasGet

func (b *LocalBackend) PersonasGet(ctx context.Context, slug models.Slug) (*models.Persona, error)

func (*LocalBackend) PersonasList

func (b *LocalBackend) PersonasList(ctx context.Context) ([]models.Persona, error)

func (*LocalBackend) PersonasUpsert

func (b *LocalBackend) PersonasUpsert(ctx context.Context, slug models.Slug, content string, bundledRules []models.Slug) (*models.Persona, error)

func (*LocalBackend) RulesDelete

func (b *LocalBackend) RulesDelete(ctx context.Context, slug models.Slug) error

func (*LocalBackend) RulesGet

func (b *LocalBackend) RulesGet(ctx context.Context, slug models.Slug) (*models.Rule, error)

func (*LocalBackend) RulesList

func (b *LocalBackend) RulesList(ctx context.Context) ([]models.Rule, error)

func (*LocalBackend) RulesUpsert

func (b *LocalBackend) RulesUpsert(ctx context.Context, slug models.Slug, entries []models.RuleEntry) (*models.Rule, error)

func (*LocalBackend) SoulsDelete

func (b *LocalBackend) SoulsDelete(ctx context.Context, slug models.Slug) error

func (*LocalBackend) SoulsGet

func (b *LocalBackend) SoulsGet(ctx context.Context, slug models.Slug) (*models.Soul, error)

func (*LocalBackend) SoulsList

func (b *LocalBackend) SoulsList(ctx context.Context) ([]models.Soul, error)

func (*LocalBackend) SoulsUpsert

func (b *LocalBackend) SoulsUpsert(ctx context.Context, slug models.Slug, content string) (*models.Soul, error)

func (*LocalBackend) StateDelete

func (b *LocalBackend) StateDelete(ctx context.Context, scopeType models.ScopeType, referenceID string) error

func (*LocalBackend) StateFindLayers

func (b *LocalBackend) StateFindLayers(ctx context.Context, projectSlug string) ([]models.LayerOverride, error)

func (*LocalBackend) StateResolveForScope

func (b *LocalBackend) StateResolveForScope(ctx context.Context, projectSlug string) (*models.EffectiveState, error)

func (*LocalBackend) StateSet

func (b *LocalBackend) StateSet(ctx context.Context, scopeType models.ScopeType, referenceID string, partial *models.LayerOverride) error

func (*LocalBackend) VersionsGet

func (b *LocalBackend) VersionsGet(ctx context.Context, contentType models.ContentType, slug models.Slug, version int) (*models.ContentVersion, error)

func (*LocalBackend) VersionsList

func (b *LocalBackend) VersionsList(ctx context.Context, contentType models.ContentType, slug models.Slug) ([]models.VersionSummary, error)

func (*LocalBackend) WithWorkspace

func (b *LocalBackend) WithWorkspace(ws models.WorkspaceID) *LocalBackend

WithWorkspace returns a shallow clone of b bound to ws. Every app field and the shared DB handle are kept; only the workspace id changes. Used by surfaces that dispatch requests across multiple workspaces (e.g. the HTTP-mounted MCP endpoint) without paying for a full re-open of the database.

func (*LocalBackend) WorkspaceCreate

func (b *LocalBackend) WorkspaceCreate(ctx context.Context, name string) (*models.Workspace, error)

func (*LocalBackend) WorkspaceDelete

func (b *LocalBackend) WorkspaceDelete(ctx context.Context, id models.WorkspaceID) error

func (*LocalBackend) WorkspaceGetByName

func (b *LocalBackend) WorkspaceGetByName(ctx context.Context, name string) (*models.Workspace, error)

func (*LocalBackend) WorkspaceID

func (b *LocalBackend) WorkspaceID() models.WorkspaceID

WorkspaceID returns the workspace bound to this backend.

func (*LocalBackend) WorkspaceList

func (b *LocalBackend) WorkspaceList(ctx context.Context) ([]models.Workspace, error)

func (*LocalBackend) WorkspacePurge

func (b *LocalBackend) WorkspacePurge(ctx context.Context, id models.WorkspaceID) error

func (*LocalBackend) WorkspaceRename

func (b *LocalBackend) WorkspaceRename(ctx context.Context, id models.WorkspaceID, newName string) (*models.Workspace, error)

type RemoteBackend

type RemoteBackend struct {
	// contains filtered or unexported fields
}

RemoteBackend talks to a brainjar server over MCP Streamable HTTP. The bound workspace is attached to every request as the X-Brainjar-Workspace header; the secret resolver is invoked per request so key rotation takes effect without a CLI restart. One MCP session is opened lazily on first call and reused for the backend's lifetime; Close tears it down.

func NewRemote

func NewRemote(cfg RemoteConfig, resolver secrets.Resolver) (*RemoteBackend, error)

NewRemote builds a RemoteBackend against the given config. Returns BadRequest when the URL is not https, missing required fields, or malformed.

func NewRemoteFromSession

func NewRemoteFromSession(session *mcp.ClientSession, workspaceID models.WorkspaceID) *RemoteBackend

NewRemoteFromSession builds a RemoteBackend around a pre-connected MCP session. Only for in-process tests — production goes through NewRemote so the TLS / auth / https-only guarantees hold.

func (*RemoteBackend) APIKeyCreate

func (b *RemoteBackend) APIKeyCreate(ctx context.Context, name string) (string, *models.APIKey, error)

func (*RemoteBackend) APIKeyList

func (b *RemoteBackend) APIKeyList(ctx context.Context) ([]models.APIKeySummary, error)

func (*RemoteBackend) APIKeyRevoke

func (b *RemoteBackend) APIKeyRevoke(ctx context.Context, id models.APIKeyID) error

func (*RemoteBackend) AdminExport

func (b *RemoteBackend) AdminExport(ctx context.Context) (*bundle.ContentBundle, error)

func (*RemoteBackend) AdminImport

func (*RemoteBackend) BrainsDelete

func (b *RemoteBackend) BrainsDelete(ctx context.Context, slug models.Slug) error

func (*RemoteBackend) BrainsGet

func (b *RemoteBackend) BrainsGet(ctx context.Context, slug models.Slug) (*models.Brain, error)

func (*RemoteBackend) BrainsList

func (b *RemoteBackend) BrainsList(ctx context.Context) ([]models.Brain, error)

func (*RemoteBackend) BrainsUpsert

func (b *RemoteBackend) BrainsUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, prefs *models.ModelPrefs) (*models.Brain, error)

func (*RemoteBackend) Close

func (b *RemoteBackend) Close() error

Close tears down the MCP session if one was opened.

func (*RemoteBackend) ComposePrompt

func (*RemoteBackend) PersonasDelete

func (b *RemoteBackend) PersonasDelete(ctx context.Context, slug models.Slug) error

func (*RemoteBackend) PersonasGet

func (b *RemoteBackend) PersonasGet(ctx context.Context, slug models.Slug) (*models.Persona, error)

func (*RemoteBackend) PersonasList

func (b *RemoteBackend) PersonasList(ctx context.Context) ([]models.Persona, error)

func (*RemoteBackend) PersonasUpsert

func (b *RemoteBackend) PersonasUpsert(ctx context.Context, slug models.Slug, content string, bundledRules []models.Slug) (*models.Persona, error)

func (*RemoteBackend) RulesDelete

func (b *RemoteBackend) RulesDelete(ctx context.Context, slug models.Slug) error

func (*RemoteBackend) RulesGet

func (b *RemoteBackend) RulesGet(ctx context.Context, slug models.Slug) (*models.Rule, error)

func (*RemoteBackend) RulesList

func (b *RemoteBackend) RulesList(ctx context.Context) ([]models.Rule, error)

func (*RemoteBackend) RulesUpsert

func (b *RemoteBackend) RulesUpsert(ctx context.Context, slug models.Slug, entries []models.RuleEntry) (*models.Rule, error)

func (*RemoteBackend) SoulsDelete

func (b *RemoteBackend) SoulsDelete(ctx context.Context, slug models.Slug) error

func (*RemoteBackend) SoulsGet

func (b *RemoteBackend) SoulsGet(ctx context.Context, slug models.Slug) (*models.Soul, error)

func (*RemoteBackend) SoulsList

func (b *RemoteBackend) SoulsList(ctx context.Context) ([]models.Soul, error)

func (*RemoteBackend) SoulsUpsert

func (b *RemoteBackend) SoulsUpsert(ctx context.Context, slug models.Slug, content string) (*models.Soul, error)

func (*RemoteBackend) StateDelete

func (b *RemoteBackend) StateDelete(ctx context.Context, scopeType models.ScopeType, referenceID string) error

func (*RemoteBackend) StateFindLayers

func (b *RemoteBackend) StateFindLayers(ctx context.Context, projectSlug string) ([]models.LayerOverride, error)

func (*RemoteBackend) StateResolveForScope

func (b *RemoteBackend) StateResolveForScope(ctx context.Context, projectSlug string) (*models.EffectiveState, error)

func (*RemoteBackend) StateSet

func (b *RemoteBackend) StateSet(ctx context.Context, scopeType models.ScopeType, referenceID string, partial *models.LayerOverride) error

func (*RemoteBackend) VersionsGet

func (b *RemoteBackend) VersionsGet(ctx context.Context, contentType models.ContentType, slug models.Slug, v int) (*models.ContentVersion, error)

func (*RemoteBackend) VersionsList

func (b *RemoteBackend) VersionsList(ctx context.Context, contentType models.ContentType, slug models.Slug) ([]models.VersionSummary, error)

func (*RemoteBackend) WorkspaceCreate

func (b *RemoteBackend) WorkspaceCreate(ctx context.Context, name string) (*models.Workspace, error)

func (*RemoteBackend) WorkspaceDelete

func (b *RemoteBackend) WorkspaceDelete(ctx context.Context, id models.WorkspaceID) error

func (*RemoteBackend) WorkspaceGetByName

func (b *RemoteBackend) WorkspaceGetByName(ctx context.Context, name string) (*models.Workspace, error)

func (*RemoteBackend) WorkspaceID

func (b *RemoteBackend) WorkspaceID() models.WorkspaceID

WorkspaceID returns the workspace bound to this backend.

func (*RemoteBackend) WorkspaceList

func (b *RemoteBackend) WorkspaceList(ctx context.Context) ([]models.Workspace, error)

func (*RemoteBackend) WorkspacePurge

func (b *RemoteBackend) WorkspacePurge(ctx context.Context, id models.WorkspaceID) error

func (*RemoteBackend) WorkspaceRename

func (b *RemoteBackend) WorkspaceRename(ctx context.Context, id models.WorkspaceID, newName string) (*models.Workspace, error)

type RemoteConfig

type RemoteConfig struct {
	URL            string
	WorkspaceID    models.WorkspaceID
	APIKeyRef      string
	DialTimeout    time.Duration
	RequestTimeout time.Duration
	// HTTPClient overrides the default transport. Primarily a test hook
	// so httptest.Server.Client() (with its self-signed cert) validates;
	// production leaves it nil and NewRemote builds a client using the
	// system trust store.
	HTTPClient *http.Client
}

RemoteConfig is the minimum data RemoteBackend needs at construction.

Jump to

Keyboard shortcuts

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