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 aVerifiedIdentity— the output of a verifier, never a raw token. Your adapter owns routing (which connection, therefore which tenant), role mapping, and the JIT decision. Studyadapters/sso-oidc/index.ts; most "IdP adapters" turn out to be aVerifiedIdentitymapping plus that resolver, in which case write the mapping and usesso-oidcrather 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, andlast_ownersurfacing as a 409 the IdP shows an admin. - Your system consumes roles → implement
RoleBridgeover aTupleStore. 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:
- 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.
- 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).
last_ownerhas 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.- UserIds are namespaced by default, with
mapUseras the documented, deliberate way out. - 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. - 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.tsfaithfully (match the core'sTenancyErrorcodes) in the same PR. - 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.