Contributing an adapter

Synced from tenant-kit-adapters/CONTRIBUTING.md — the repo is canonical.

cp -r adapters/_template adapters/<your-system>
pnpm typecheck && pnpm test

Pick one seam. An integration that spans seams is two adapters sharing a folder; give each its own.

Which seam is yours?

  • Your system says who is signing in → implement SsoResolver. Input is a VerifiedIdentity — the output of a verifier, never a raw token. Your adapter owns routing (which connection, therefore which tenant), role mapping, and the JIT decision. Study adapters/sso-oidc/index.ts; most "IdP adapters" turn out to be a VerifiedIdentity mapping plus that resolver, in which case write the mapping and use sso-oidc rather than duplicating it.
  • Your system pushes users in and out → implement ScimDirectory (or, for a non-SCIM protocol, the same shape over your protocol's requests). The lifecycle to honor: lookup before create, replay convergence, deactivate-as-deprovision, and last_owner surfacing as a 409 the IdP shows an admin.
  • Your system consumes roles → implement RoleBridge over a TupleStore. Convergent desired-state sync, scoped to the object you read; delete what memberships don't explain, touch nothing else.

The review checklist

Every PR is read against these, in order:

  1. No credential verification in the adapter. If your diff contains signature checking, JWKS fetching, or an HTTP client for a token endpoint, the adapter is doing the verifier's job.
  2. Replay every operation in your tests. Provision twice, resolve twice, sync twice. Same state once; the second report/status says "nothing to do" (or converges a changed input, and the test shows which).
  3. last_owner has a test. Whatever your seam, there is a path where the external system tries to remove or demote the only owner. Show the refusal surfacing in your protocol's vocabulary — never a forced removal, never a swallowed error.
  4. UserIds are namespaced by default, with mapUser as the documented, deliberate way out.
  5. Configuration is arguments. No module state, no process.env, no singletons — two instances of your adapter must coexist in one process, same as the core.
  6. Tests run on the memory testkit. No network, no Postgres, no provider sandbox. If the testkit lacks a behaviour you need, extend testkit/memory-tenancy.ts faithfully (match the core's TenancyError codes) in the same PR.
  7. The file explains itself. House style: comments state the constraint and the failure it prevents, not what the next line does. If a choice was between two defensible options, the comment names the loser and why.

What gets an adapter rejected

  • Verifying credentials (rule 1) — resubmit with the verification deleted and the input typed as its output.
  • A seam-spanning grab bag — split it.
  • Moving the core's scope line ("just add a users table", "just one more role") — that discussion belongs on a tenant-kit issue, not in an adapter PR.