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.

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. 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. An external plugin normally has its own go.mod, depends on plugin-api, and may depend on narrowly required core packages when it needs database or sandbox helpers. Construct it from a private distribution entry point; add its module to go.work only while developing it beside Suchi. 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.