Skip to main content
Suchi plugins are Go packages compiled into a distribution. There is no runtime shared-object loader or plugin marketplace inside the server. This keeps the runtime dependency graph and executable code visible at build time. plugin-api/ contains the small set of interfaces shared by core and plugins: Principal, Event, AuditEvent, and BlobRef are boundary value types. Business services, database helpers, and format-specific contracts remain in core rather than expanding plugin-api. Preserve filing ownership through these values: Event.SystemID and AuditEvent.SystemID identify system/document work; zero is reserved for genuinely instance-wide work. A documentless index refresh or approval proposal is still system-owned. Principal.TokenSystemID is the authenticated token’s binding, not the browser’s selected context; session principals leave it zero.

Application assembly

Compiled distributions can import github.com/johnnybravo-xyz/suchi/distro/app and call app.Run(ctx, options). The caller resolves config.Config (normally with config.LoadFile then config.Load), supplies its logger/build identity, and owns signal cancellation and process exit codes. The community command uses this same entry point. No copy of the command’s server wiring is required.
Here receiptAction is a compiled definition as described in Automations. Options add to the built-ins; duplicate kinds fail rather than replacing them. Migrations finish before the registry is built. Core routes and subscribers are then assembled before Configure runs exactly once. No dispatcher, watcher, scheduler, retention loop, mail supervisor or HTTP listener has started at that point. A callback error aborts startup and closes the database without starting runtime work. No extension options are needed for a complete local archive. PipelineProposalVersions is distribution release policy rather than mutable server configuration. Zero fields create no new processing-update reminders. A compiled distribution that opts in supplies checked-in per-pipeline recommendation thresholds no greater than the pipeline revisions in that build; thresholds newer than the compiled pipeline revisions stop startup. Keep this policy in the tagged source so the same tag cannot produce artifacts with different proposal behavior. Services contains only the instance’s DB, HTTP Mux, durable Jobs dispatcher, Approvals engine, immutable Actions registry and Log. Compiled distributions use it to add native routes and register durable subscribers. The fields are instance-owned; registration in one assembled application does not affect another. Configure routes pass through the normal middleware, but they are absent from the explicit token allowlist and therefore remain session-only. Route handlers still own their role, ACL and system checks. MigrationSet holds a stable component name and the complete ordered []db.Migration history, starting at version 1 without gaps. Names match [a-z][a-z0-9_-]{0,63}; core is reserved. SQL cannot be empty. Keep applied SQL and RebuildTables unchanged; add a new version for changes. Each component advances independently in _suchi_extension_migrations, with checksums and application timestamps. Public core versions remain in PRAGMA user_version; never append or renumber extension steps into that sequence. An unchanged rerun is a no-op; drift or a binary missing an applied version fails before new steps in that set. Omitting a component does not remove its stored schema/history. Migration SQL and callbacks are trusted compiled code. Migration SQL must not manage transactions, write user_version, or alter the reserved _suchi_ ledger namespace. Each step uses the sole writer and records its ledger row atomically. Rebuild migrations retain core’s foreign-key checking/restoration convention. A failed step leaves earlier completed versions intact and rolls back its own schema, data and applied record. See Storage and transaction invariants and Runtime, jobs, and integrations for storage and boot ownership. Each extension owns the explicit tables declared by its migration set; there is no generic core plugin_kv store. During a core compatibility table rebuild, Suchi preserves extension-created indexes and triggers attached to core tables by temporarily removing them and replaying their exact DDL. A dependency on a retired core object aborts adoption atomically rather than dropping extension data or schema. This is one compile-time composition callback, not runtime plugin loading or a general registration framework. It does not expose the CAS, API server or a service lookup mechanism. The lower-level subscriber/authenticator/sink contracts below remain explicit code dependencies.

Subscriber contract

The dispatcher may retry an event, so Handle must be idempotent. Persist a deduplication key or use a unique database constraint before applying an observable side effect. Respect context cancellation and return failures so the job can retry or become visible as dead. Do network and subprocess work outside a database transaction. Open one short write transaction only after the external result is available. State needed after restart belongs in the database or event payload, not a package-level cache. Enqueue through jobs.Enqueue(ctx, tx, kind, docID, systemID, payload): the explicit system must match any document. Zero persists as NULL only for global work. The outbox inserts jobs without deduplicating them. Resolve metadata and background actions within the event/document system, rechecking current referenced records in the writer; a job is not an authenticated cross-system document-link grant. Minimal shape:
Register the handler while assembling the binary:
Every produced job kind must have a registered consumer. An unknown kind otherwise remains unclaimed in the outbox.

Authenticator contract

Return (nil, nil) when the request does not use your authentication scheme so the next authenticator can try. Return an error when your scheme is present but invalid; silently falling through would allow an authentication downgrade. An authenticator should resolve the external identity to a Suchi user and return only the principal fields needed by authorization and audit. Put roles, group membership, and object policy in the authorization layer rather than in provider-specific token parsing. Token authenticators must set TokenSystemID; an external token that leaves it zero is restricted to original system 1, never all systems. Local tokens also carry TokenID so mutations can recheck revocation, actor and binding inside the single writer, plus the server-selected TokenSource issuance provenance. Authentication does not grant membership or document permission; even administrator tokens cannot escape their bound system or an explicit target. Local issuance uses the caller-owned transaction contract IssueAPIToken(ctx context.Context, tx *sql.Tx, userID, systemID int64, name, scopes, source string) (string, error). The API’s TokenIssuer seam has the same signature. Keep pairing consumption, current membership checks and issuance in that transaction so failure rolls back consumption and membership removal cannot be bypassed by a later re-admission. Register more specific authenticators before general local session and token handling. See plugins/oidc and plugins/local-auth for shipped examples.

Audit sink contract

Register with audit.RegisterSink. The local audit_events table remains the durable record. Sink delivery is best effort and happens after the database write, so Emit must be fast or hand work to its own bounded buffer. Never add document content or plaintext credentials to an audit event.

Package and build wiring

Shipped plugins live in the root module. A compiled distribution assembles the plugins it includes through app.Options; integrations may use plugin-api and narrowly required core packages for explicit database or sandbox behavior. Rebuild and test the final binary after changing plugins. Dependencies become part of that binary, so prefer the standard library and existing helpers.

Boundaries

  • Do not rewrite original CAS blobs. Store a derived blob and update metadata.
  • Use core/sandbox for external commands rather than raw os/exec.
  • Keep write transactions free of HTTP, model, and subprocess calls.
  • Do not create a plugin interface for behavior owned by one shipped core package.
  • Test subscribers directly with a migrated temporary database; test authenticators with httptest requests.
Reference implementations include plugins/local-auth, plugins/oidc, plugins/llm-classifier, and core/pipeline/postingest.