Govern Specv0.1
Spec v0.1 — draft

Identity Contract

Govern’s auth path is currently rooted in Microsoft Entra. The published Connector spec defines an IDP-neutral identity contract so external implementers using Okta, Auth0, Google Workspace, or in-house SSO can plug in without changing connector code. The reference implementation stays Entra-rooted; only the contract narrows to its IDP-neutral subset.

Field names like attorney_oid and entra_tenant_id stay intact in storage (no migration). Their meaning narrows: attorney_oid becomes “IDP-native stable subject ID”; entra_tenant_id becomes “IDP-specific tenant context, optional.”

Required claims

A compliant identity provider MUST mint access tokens with the following claims. Tokens missing any of these are rejected at the gateway with a structured invalid_token error.

ClaimTypeDescription
substringStable IDP-native subject identifier for the human or service account. Persistent across sessions. Stored as attorney_oid in Govern internals.
issstringIssuer URL. The runtime verifier checks this against the firm’s configured IDP issuer.
audstringAudience. Must match the connector’s configured client identifier.
iatnumberIssued-at, seconds since epoch.
expnumberExpiry, seconds since epoch. Govern rejects tokens with now > exp (with 60s clock-skew tolerance).
firm_idstringGovern firm-scope identifier. Issued by Govern at firm-bootstrap; injected into the token by the IDP via claim mapping. Tenant-isolation contract requires firm_id on every authenticated request.

Optional IDP-specific claims

These are used opportunistically. Connectors that don’t surface them MUST NOT fail closed on their absence.

ClaimTypeDescription
emailstringDisplay email. Surfaced in the audit feed and admin UI only. Not used for authorization.
app_rolesstring[]Authorization roles. Govern checks for MCP.GovernAdmin / MCP.GovernReviewer to gate admin surfaces. Other roles are ignored.
entra_tenant_idstringThe IDP-specific tenant context (tid for Entra, Okta org ID, Auth0 tenant, etc.). Stamped on every ledger row for tenant-isolation cross-checks. Other IDPs: mint an analogous claim and map it to this column.
granted_scopesstring[]OAuth scopes the token was minted with. Future scope-gated tools may narrow per-tool.
idp_kindstringReserved — not yet minted. Discriminator for the IDP that issued this token (entra, okta, auth0, generic-oidc). Becomes the type-discriminator on IdentityClaims when the second IDP adapter ships.

Token shapes

The Govern gateway accepts two token shapes:

  1. IDP-native access token — at OAuth/OIDC bridge endpoints. Verified against the IDP’s published JWKS. Used to bootstrap a Govern session.
  2. Govern-minted MCP token (HS256, signed with JWT_SIGNING_KEYin Cloudflare secrets) — for in-session API calls. Carries the same IdentityClaims shape.

A connector implementing the identity port MUST handle both shapes — the OIDC verifier branch for IDP-native tokens, the symmetric verifier branch for Govern-minted MCP tokens.

Storage contract

The audit ledger schema preserves Entra-shaped column names for backward compatibility:

A connector implementing the contract for a non-Entra IDP MUST map its claims into this storage shape. Renaming the columns to idp_tenant_id was considered and rejected — the data-migration cost outweighs the cosmetic clarity.

Implementing a new IDP adapter

A connector adapter for a new IDP implements the identity contract by providing:

  1. JWKS / verification keys — typically fetched from {issuer}/.well-known/openid-configuration and cached.
  2. Issuer + audience config stored per-firm in govern_firm_idp_config.
  3. Claim mapper translating IDP-native claims to the required + optional set:
    interface IdentityAdapter {
      verify(rawToken: string): Promise<IdentityClaims>;
      mintMcpToken(claims: IdentityClaims, ttlSeconds: number): Promise<string>;
    }
  4. Tenant-context resolver mapping the IDP-specific tenant to the entra_tenant_id storage column.
  5. (Optional) role-grant API — Microsoft Graph for Entra, Okta API for Okta. Used by /admin/users to grant/revoke Govern app-roles. Connectors without a role-grant API operate in roster-only mode.

Determinism boundary

Authorization decisions are deterministic. The identity contract is the input side of that boundary: claims feed into the policy engine, the engine emits a decision, the decision is logged. ML-based identity confidence scores are NOT load-bearing — if an IDP exposes such a signal, Govern may surface it as metadata but never gates on it.

The verify step is a pure boolean: token is valid (claims trusted) or invalid (request rejected). No “probably-valid” branch.

Versioning

The identity contract is versioned with the v1 Connector spec. Breaking changes to required claims, the storage contract, or the verify/mint signatures bump the spec major version. Adding a new optional claim is non-breaking. Adding a new required claim is breaking. Renames are breaking.

idp_kind is reserved but not yet minted. When the second IDP adapter ships, adding it as a discriminator on IdentityClaims is non-breaking — every Entra token gets idp_kind: ‘entra’ retroactively in claim normalization; no on-disk migration required.

Reference implementation