backend

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: May 2, 2026 License: MIT Imports: 27 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
	SoulList(ctx context.Context) ([]models.Soul, error)
	SoulGet(ctx context.Context, slug models.Slug) (*models.Soul, error)
	SoulUpsert(ctx context.Context, slug models.Slug, content string) (*models.Soul, error)
	SoulDelete(ctx context.Context, slug models.Slug) error

	// Personas
	PersonaList(ctx context.Context) ([]models.Persona, error)
	PersonaGet(ctx context.Context, slug models.Slug) (*models.Persona, error)
	PersonaUpsert(ctx context.Context, slug models.Slug, content string, bundledRules []models.Slug) (*models.Persona, error)
	PersonaDelete(ctx context.Context, slug models.Slug) error

	// Rules
	RuleList(ctx context.Context) ([]models.Rule, error)
	RuleGet(ctx context.Context, slug models.Slug) (*models.Rule, error)
	RuleUpsert(ctx context.Context, slug models.Slug, entries []models.RuleEntry) (*models.Rule, error)
	RuleDelete(ctx context.Context, slug models.Slug) error

	// Brains
	BrainList(ctx context.Context) ([]models.Brain, error)
	BrainGet(ctx context.Context, slug models.Slug) (*models.Brain, error)
	BrainUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, procedureSlug models.Slug, skillSlugs []models.Slug, prefs *models.ModelPrefs) (*models.Brain, error)
	BrainDelete(ctx context.Context, slug models.Slug) error

	// Procedures
	ProcedureList(ctx context.Context) ([]models.Procedure, error)
	ProcedureGet(ctx context.Context, slug models.Slug) (*models.Procedure, error)
	ProcedureUpsert(ctx context.Context, slug models.Slug, content string) (*models.Procedure, error)
	ProcedureDelete(ctx context.Context, slug models.Slug) error

	// Skills
	SkillList(ctx context.Context) ([]models.Skill, error)
	SkillGet(ctx context.Context, slug models.Slug) (*models.Skill, error)
	SkillUpsert(ctx context.Context, slug models.Slug, description, body string, triggers []string, version int, scope models.SkillScope) (*models.Skill, error)
	SkillDelete(ctx context.Context, slug models.Slug) error
	SkillAttach(ctx context.Context, brainSlug, skillSlug models.Slug, position int) error
	SkillDetach(ctx context.Context, brainSlug, skillSlug 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
	VersionList(ctx context.Context, contentType models.ContentType, slug models.Slug) ([]models.VersionSummary, error)
	VersionGet(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) BrainDelete added in v0.7.0

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

func (*LocalBackend) BrainGet added in v0.7.0

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

func (*LocalBackend) BrainList added in v0.7.0

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

func (*LocalBackend) BrainUpsert added in v0.7.0

func (b *LocalBackend) BrainUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, procedureSlug models.Slug, skillSlugs []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) PersonaDelete added in v0.7.0

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

func (*LocalBackend) PersonaGet added in v0.7.0

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

func (*LocalBackend) PersonaList added in v0.7.0

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

func (*LocalBackend) PersonaUpsert added in v0.7.0

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

func (*LocalBackend) ProcedureDelete added in v0.7.0

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

func (*LocalBackend) ProcedureGet added in v0.7.0

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

func (*LocalBackend) ProcedureList added in v0.7.0

func (b *LocalBackend) ProcedureList(ctx context.Context) ([]models.Procedure, error)

func (*LocalBackend) ProcedureUpsert added in v0.7.0

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

func (*LocalBackend) RuleDelete added in v0.7.0

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

func (*LocalBackend) RuleGet added in v0.7.0

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

func (*LocalBackend) RuleList added in v0.7.0

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

func (*LocalBackend) RuleUpsert added in v0.7.0

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

func (*LocalBackend) SkillAttach added in v0.7.0

func (b *LocalBackend) SkillAttach(ctx context.Context, brainSlug, skillSlug models.Slug, position int) error

SkillAttach links an existing skill to an existing brain at the requested position. Both slugs must already exist in the workspace; the underlying store rejects unknown rows with a foreign-key error that surfaces as an apperror. Re-attaching the same pair updates the position via the store's UPSERT semantics.

func (*LocalBackend) SkillDelete added in v0.7.0

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

func (*LocalBackend) SkillDetach added in v0.7.0

func (b *LocalBackend) SkillDetach(ctx context.Context, brainSlug, skillSlug models.Slug) error

SkillDetach unlinks a skill from a brain. No-op when the link is already absent — the store's DELETE returns sql.ErrNoRows-equivalent rather than a brainjar error.

func (*LocalBackend) SkillGet added in v0.7.0

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

func (*LocalBackend) SkillList added in v0.7.0

func (b *LocalBackend) SkillList(ctx context.Context) ([]models.Skill, error)

func (*LocalBackend) SkillUpsert added in v0.7.0

func (b *LocalBackend) SkillUpsert(ctx context.Context, slug models.Slug, description, body string, triggers []string, version int, scope models.SkillScope) (*models.Skill, error)

func (*LocalBackend) SoulDelete added in v0.7.0

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

func (*LocalBackend) SoulGet added in v0.7.0

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

func (*LocalBackend) SoulList added in v0.7.0

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

func (*LocalBackend) SoulUpsert added in v0.7.0

func (b *LocalBackend) SoulUpsert(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) VersionGet added in v0.7.0

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

func (*LocalBackend) VersionList added in v0.7.0

func (b *LocalBackend) VersionList(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) BrainDelete added in v0.7.0

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

func (*RemoteBackend) BrainGet added in v0.7.0

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

func (*RemoteBackend) BrainList added in v0.7.0

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

func (*RemoteBackend) BrainUpsert added in v0.7.0

func (b *RemoteBackend) BrainUpsert(ctx context.Context, slug, soulSlug, personaSlug models.Slug, ruleSlugs []models.Slug, procedureSlug models.Slug, skillSlugs []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) PersonaDelete added in v0.7.0

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

func (*RemoteBackend) PersonaGet added in v0.7.0

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

func (*RemoteBackend) PersonaList added in v0.7.0

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

func (*RemoteBackend) PersonaUpsert added in v0.7.0

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

func (*RemoteBackend) ProcedureDelete added in v0.7.0

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

func (*RemoteBackend) ProcedureGet added in v0.7.0

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

func (*RemoteBackend) ProcedureList added in v0.7.0

func (b *RemoteBackend) ProcedureList(ctx context.Context) ([]models.Procedure, error)

func (*RemoteBackend) ProcedureUpsert added in v0.7.0

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

func (*RemoteBackend) RuleDelete added in v0.7.0

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

func (*RemoteBackend) RuleGet added in v0.7.0

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

func (*RemoteBackend) RuleList added in v0.7.0

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

func (*RemoteBackend) RuleUpsert added in v0.7.0

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

func (*RemoteBackend) SkillAttach added in v0.7.0

func (b *RemoteBackend) SkillAttach(ctx context.Context, brainSlug, skillSlug models.Slug, position int) error

func (*RemoteBackend) SkillDelete added in v0.7.0

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

func (*RemoteBackend) SkillDetach added in v0.7.0

func (b *RemoteBackend) SkillDetach(ctx context.Context, brainSlug, skillSlug models.Slug) error

func (*RemoteBackend) SkillGet added in v0.7.0

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

func (*RemoteBackend) SkillList added in v0.7.0

func (b *RemoteBackend) SkillList(ctx context.Context) ([]models.Skill, error)

func (*RemoteBackend) SkillUpsert added in v0.7.0

func (b *RemoteBackend) SkillUpsert(ctx context.Context, slug models.Slug, description, body string, triggers []string, version int, scope models.SkillScope) (*models.Skill, error)

func (*RemoteBackend) SoulDelete added in v0.7.0

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

func (*RemoteBackend) SoulGet added in v0.7.0

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

func (*RemoteBackend) SoulList added in v0.7.0

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

func (*RemoteBackend) SoulUpsert added in v0.7.0

func (b *RemoteBackend) SoulUpsert(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) VersionGet added in v0.7.0

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

func (*RemoteBackend) VersionList added in v0.7.0

func (b *RemoteBackend) VersionList(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
	// CACertPath, when set, names a PEM file whose certificates are
	// trusted in addition to the system trust store. Verification stays
	// on; this is for private CAs, not skip-verify.
	CACertPath string
	// 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