Skip to content
OUTIS DOCS

Build your own

An integration is one Go value that imports github.com/outis-auth/outis-sdk, implements Integration (a Manifest()) and whichever capabilities it has. The core finds them by type assertion, so there’s no method to stub. The module has no dependencies.

Interface Implement it to
Renderer, with Manifest.Actions Provide actions. Each is an ActionSpec: an id, a label, and the policy envelope a new organization starts from. The renderer draws each request for the panel.
Responder Tell the system that asked what was decided, with one idempotent call on every outcome.
Proposer Turn an outside event into a request, for your own actions or another integration’s. A SelfVerifying proposer names routes the platform signs itself, mounted outside the API key guard.
Notifier Carry the arming code to an operator. A LastResort notifier reaches no person and sorts behind every channel that does.
Configurable Declare the settings you read from the environment, so the dashboard can say which are unset without showing their values.
IdentityFlow Prove which account on your platform an operator holds, through the platform’s own OAuth sign in.

Validate checks the combination: actions need a renderer, a renderer or responder needs actions, and an integration does at least one thing.

A proposer proposes within the envelope policy allows and never sets a quorum. An inbound call belongs to an organization only through a connection the core proved or an id the platform signed; otherwise the proposal leaves Org empty and the core fills it from the API key. Nothing Outis holds, like a webhook secret, is handed to a customer.

A Responder makes at most one call, bound to what the operators saw: merge this pull request at this sha, submit this review, answer this pending deployment. It never retries and never acts on a head that moved. A refusal from that system lands the request in failed.

Every integration runs sdktest.Run as its test, supplying a constructor with faked transports, representative params per action, and how to build a request its proposer accepts. The suite checks that an arming code never reaches the log, that one notify is one send, and that the same decision handed over twice is delivered once.

func TestConformance(t *testing.T) {
    sdktest.Run(t, sdktest.Suite{
        New: func(t *testing.T, log *slog.Logger) sdk.Integration {
            return New(fakeTransport(t), log)
        },
        Params: map[string]map[string]string{
            "example.release": {"service": "api", "version": "2.4.0"},
        },
    })
}

A rule every integration should follow belongs in sdktest, so every integration gets it.

Each integration ships a README on the shared template, opening with what it provides: actions, proposes, notifies, responds. The server reads its settings from the environment, so its routes exist from the first boot and answer with what’s missing until the variables are set.