adminapi

package
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Overview

Package adminapi exposes a read-only admin HTTP surface over the fabriq query facade. It is designed for consumption by the fabriq-admin SPA and any operator tooling that needs a stable, auth-agnostic read API over the data fabric.

The extension is auth-agnostic: the host MUST attach authentication and tenant-injection middleware via WithRouteOptions. Fabriq adminapi adds no auth of its own so that the host controls the security boundary entirely.

Phase 1 endpoints:

GET {BasePath}/meta            → service metadata + capabilities
GET {BasePath}/entities        → paginated entity list (requires ?type=)
GET {BasePath}/entities/{id}   → single entity by type and id (requires ?type=)
GET {BasePath}/capabilities    → fabric subsystem capabilities (instance-level,
                                 or per-type with ?type=)

Index

Constants

View Source
const APIVersion = 1

APIVersion is the current major version of the adminapi wire contract. Clients may advertise the version they were built against via the X-Fabriq-Api-Version request header; the auth middleware rejects requests whose advertised major differs from this value (HTTP 426). Responses always carry the current APIVersion in the same header.

View Source
const PluginRemoteEntityType = "admin_plugin_remote"

PluginRemoteEntityType is the well-known dynamic entity type name for remote plugin specs. The host application must register a matching EntitySpec (with a DynamicSchema) before using these endpoints; see PluginRemoteSpec.

View Source
const Version = forgeext.Version

Version is the adminapi extension version.

Variables

This section is empty.

Functions

func PluginRemoteSpec

func PluginRemoteSpec() registry.EntitySpec

PluginRemoteSpec returns the canonical EntitySpec for admin_plugin_remote. The host application calls reg.Register(adminapi.PluginRemoteSpec()) during startup so the entity is available to the command plane and the relational querier. In unit tests, register it via buildPluginWorld or equivalent.

Types

type Extension

type Extension struct {
	forge.BaseExtension
	// contains filtered or unexported fields
}

Extension exposes the fabriq data fabric as a read-only admin HTTP surface. It depends on the "fabriq" extension and resolves its query facade at Start.

func NewAdminAPI

func NewAdminAPI(fab *forgeext.Extension, opts ...Option) *Extension

NewAdminAPI builds the adminapi extension wired to a started fabriq Extension. The endpoint is auth-agnostic: the host MUST attach authentication and tenant-injection middleware via WithRouteOptions.

func (*Extension) Dependencies

func (e *Extension) Dependencies() []string

Dependencies implements forge.Extension.

func (*Extension) Description

func (e *Extension) Description() string

Description implements forge.Extension.

func (*Extension) Name

func (e *Extension) Name() string

Name implements forge.Extension.

func (*Extension) Register

func (e *Extension) Register(app forge.App) error

Register registers the admin controller routes; the fabric resolves lazily in Start.

func (*Extension) Start

func (e *Extension) Start(ctx context.Context) error

Start resolves the fabriq facade from the started fabriq extension.

func (*Extension) Stop

func (e *Extension) Stop(_ context.Context) error

Stop stops the extension.

func (*Extension) Version

func (e *Extension) Version() string

Version implements forge.Extension.

type IssuedKey

type IssuedKey struct {
	ID     string
	Prefix string
	Key    string
}

IssuedKey is returned once, at issue time. Key is the plaintext bearer token and is never persisted or recoverable afterwards — only its hash is stored.

type KeyRecord

type KeyRecord struct {
	ID            string
	Prefix        string
	TenantID      string
	Label         string
	CanManageKeys bool
	CreatedAt     time.Time
	RevokedAt     *time.Time
	// ExpiresAt is nil for keys that never expire. Session tokens (issued via
	// IssueSession) always set it; the authn middleware denies a resolved key
	// once ExpiresAt is in the past, mirroring how RevokedAt is enforced.
	ExpiresAt *time.Time
}

KeyRecord is a stored key as seen on the read paths. It deliberately carries NO hash or plaintext field: redaction is structural, so List can never leak a verifiable secret. TenantID == "" means the key is multi-tenant (NULL in the database).

type KeySpec

type KeySpec struct {
	Label         string
	TenantID      string
	CanManageKeys bool
}

KeySpec describes a key to be issued. TenantID == "" issues a multi-tenant key (stored as NULL tenant_id, callable against any tenant); a non-empty TenantID scopes the key to that single tenant.

type KeyStore

type KeyStore interface {
	// Issue mints a new key, persists its hash, and returns the plaintext once.
	Issue(ctx context.Context, spec KeySpec) (IssuedKey, error)
	// IssueSession mints a multi-tenant, manage-keys session token that
	// expires after ttl. It is the foundation for dashboard login: a session
	// is just an expiring KeyStore row, validated by the same middleware path
	// as any other API key.
	IssueSession(ctx context.Context, ttl time.Duration) (IssuedKey, error)
	// Lookup resolves a key by its sha256 hex hash with NO tenant scoping.
	// Revoked keys still return (found=true): revocation is enforced by the
	// middleware, not by hiding the row.
	Lookup(ctx context.Context, keyHash string) (rec KeyRecord, found bool, err error)
	// List returns every key as a redacted KeyRecord (no hash/plaintext).
	List(ctx context.Context) ([]KeyRecord, error)
	// Revoke stamps revoked_at on the key. The row stays visible to Lookup.
	Revoke(ctx context.Context, id string) error
}

KeyStore is the persistence port for hosted-fabriq API keys. It operates on the instance-global fabriq_api_key table (created by migration 0027), which is deliberately NOT under RLS: keys are resolved by hash before any tenant context exists, mirroring the outbox's tenant-less rationale.

func NewKeyStore

func NewKeyStore(gdb *grove.DB) KeyStore

NewKeyStore builds a KeyStore over a grove.DB. The grove MUST be backed by the pg driver (grove + pgdriver), which is fabriq's only supported backend; this mirrors postgres.OpenWithGrove's driver assertion.

type Option

type Option func(*config)

Option configures the adminapi extension.

func WithAdminLogin

func WithAdminLogin(username, password string) Option

WithAdminLogin enables the dashboard-login surface (POST {BasePath}/login + /logout): username is compared verbatim at login time and password is bcrypt-hashed immediately, at option-application time — the plaintext password is never retained. WithAdminLogin REQUIRES WithAuth: login mints a session token via KeyStore.IssueSession, so Extension.Start fails fast with an error if AdminLoginUser is set but no KeyStore was configured.

func WithAuth

func WithAuth(store KeyStore) Option

WithAuth sets the instance-global API key store, enabling the /admin/keys issue/list/revoke management routes (registered only when the store is non-nil). It does NOT install the request-verifying middleware — that is a separate, explicit step: attach authMiddleware(store, basePath) via WithRouteOptions to actually gate admin requests on a valid key.

func WithAuthDisabled

func WithAuthDisabled() Option

WithAuthDisabled turns OFF secure-by-default auth: no KeyStore is auto-provisioned and the admin API is served without authentication. Use for trusted internal deployments or tests that intentionally run keyless.

func WithBasePath

func WithBasePath(p string) Option

WithBasePath sets the admin API base path (default "/admin").

func WithEmbedder

func WithEmbedder(e agent.Embedder) Option

WithEmbedder supplies the embedding model used by the text-mode vector search endpoint (POST {BasePath}/search/vector with a {query} body). Fabriq stays model-agnostic: the host provides the implementation (Anthropic, OpenAI, a local model). When unset, text-mode vector search returns 501 and callers must use the similar-to-entity mode ({id} body) instead.

func WithRouteOptions

func WithRouteOptions(opts ...forge.RouteOption) Option

WithRouteOptions forwards forge route options (auth middleware, OpenAPI decorators) to all admin routes — the extension stays auth-agnostic.

func WithSchemaAdmin

func WithSchemaAdmin() Option

WithSchemaAdmin enables the privileged schema-ops endpoints — running/rolling back static migrations and executing ad-hoc DDL. These are instance-global, schema-owner operations; leave OFF unless the host guards the admin API.

func WithWritePolicy

func WithWritePolicy(p agent.WritePolicy) Option

WithWritePolicy sets the agent-toolkit write allowlist backing the guarded write endpoint (POST {BasePath}/agent/remember). Deny-by-default: without this option Remember permits no writes. Example: allow product creates/updates via agent.WritePolicy{Allow: map[string][]command.Op{"product": {command.OpCreate, command.OpUpdate}}}.

Jump to

Keyboard shortcuts

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