1. auth
temper
  • Temper documentation
  • 2026-07-06-cogmap-telos-differentiation
  • handoffs
  • plans
    • archived
    • evidence
  • reviews
  • specs
  • spikes
  • agents
  • auth
    • Temper auth & security
    • The authorization seam (`temper-services::auth`)
    • Cognitive-map & resource authoring authorization
    • JWT verification
    • The issuer / resource-server boundary & the machine-token contract
    • The internal reconcile channel
  • code-reviews
  • cognitive-maps
  • decisions
  • development
  • guides
    • Building a cognitive map from a large corpus
    • Using temper from Claude Desktop (and claude.ai)
    • Cloud Agent Development Guide
    • Ingesting a corpus into a context
    • Development setup
    • Drain operator queries (TraceQL)
    • Enterprise Install — Ground Up
    • Operator guide: read-only GitHub credential via a BYO App + Vercel Connect
    • Operator guide: provisioning a GitHub connection in temper
    • Installing Temper
    • Delivering L0 Kernel Cogmap Content
    • Machine Credentials
    • OpenTelemetry Setup
    • Operational Memory
    • Bootstrapping a Temper org
    • Releasing Temper
    • Self-Hosting Temper with Okta
    • Self-Hosting Temper with a SAML IdP
    • Self-Hosting Temper
    • The Slack Integration — identity, credentials, and what revocation actually means
    • Operator guide: `@temper` on Slack — end-to-end setup
    • Bootstrapping a team's self-cognition cognitive map
    • Working with Teams
    • Integrating Ruby with temper-rb
    • Deploying an Eve agent to Vercel (the steward and the citation auditor)
    • WS6 Collapsed-Schema Dev Environment
  • research
  • security
  • specs
  • doors
    • Building against Temper
    • Running Temper
    • Using Temper
  • Temper /api/query — the compositional read surface
    • Run a declared composition of situated acts.
    • Schemas
      • Composition
      • OutcomeDeclaration
      • ReturnSpec
      • Intention
      • StageNode
      • ActInvocation
      • CombineNode
      • StageInput
      • StageRelation
      • Disclosure
      • QueryResponse
      • StageResult
      • StageOutput
      • ScoreKind
      • Scoring
      • ResourceHit
      • CompositionTrace
      • StageTrace
      • InputSource
      • NarrowedBy
      • StageName
      • ActName
      • IdKind
      • IdSet
      • BoundTerm
      • Extent
      • StageDisposition
      • ActRefusal
      • PlanRefusal
      • RefusalReason
      • ActQuantity
      • QuantityScale
      • ResourceFilter
      • EdgeFilter
      • PropertyPredicate
      • PropertyOp
      • ResourceSection
      • EdgeKind
      • OrdOp
      • CogmapId
      • ContextId
      • IdProvenance
      • FacetPredicate
      • CombineOp
      • BlockId
      • MatchLocation
      • BodyStorage
      • ResourceId
      • IngestState
      • ManagedMeta
      • ProfileId
      • ResourceView
      • Polarity
      • ViaEntry
      • LensId
      • RegionId
      • CogmapRegionRow
      • RegionHit
      • StageInputTrace
      • Standing
      • ActorAuthority
      • Refusal
      • SystemAccessDetails
      • PlanRefusalDetails
      • ErrorDetails
      • ErrorDetail
      • ErrorBody
  • Temper Cloud API
    • Access
      • POST /api/access/requests — submit a join request for the gating team.
      • GET /api/access/requests/me — check own join request status.
      • DELETE /api/access/requests/me — withdraw a pending join request.
      • POST /api/access/reviews — a revoked principal asks an admin to reconsider (spec D15).
      • GET /api/access/settings — read public system settings.
    • Slack Link
      • Disconnect any principal. Operator path — offboarding and stuck users.
      • Disconnect EVERY Slack principal bound to the caller's own profile.
    • Auditor
      • auditor_dispatch
      • auditor_sweep
      • complete_auditor_job
    • Graph
      • POST /api/cogmaps/{id}/graph/slice — R4 cogmap-scoped neighborhood slice.
      • GET /api/graph/cogmaps/{id}/panorama — enter-a-cogmap Tier-0 interior.
      • GET /api/graph/contexts/composition — Beat E Tier-1: the force-graph composition of a container's (or a residual bucket's) members.
      • GET /api/graph/contexts/panorama — Beat E Tier-0: goal-container territories + residual tray.
      • GET /api/graph/home — the you→teams→cogmaps membership home.
      • GET /api/graph/regions/composition — Beat D region→resources composition drill.
    • Cognitive Maps
      • list_cognitive_maps
      • genesis
      • get_cognitive_map
      • reconcile
      • analytics
      • grant_cogmap_access
      • revoke_cogmap_access
      • materialize
      • materialize_delta
      • region_metrics
      • shape
      • bind_team
      • unbind_team
    • Contexts
      • list_contexts
      • create_context
      • get_context
      • context_materialize
      • reassign
      • context_region_metrics
      • rename
      • context_shape
      • share_team
      • unshare_team
    • Events
      • cursor
      • GET /api/graph/elements/{kind}/{id}/trail — R5 element event-trail. kind ∈ {node, edge}.
    • Facets
      • set_facet
      • Read the live facets of one edge.
      • Set a facet whose owner is an **edge** rather than a resource.
      • Read the live facets of one resource — the confirming read for a write that steers region formation and Atlas grouping.
    • Health
      • health_check
    • Ingest
      • create_ingest
      • update_ingest
      • list_blocks
      • append_block
      • finalize_resource
    • Invitations
      • accept
      • decline
      • list_mine
      • list_team_invitations
      • revoke_team_invitation
      • create_team_invitation
    • Invocations
      • list_invocations
      • open
      • show
      • close
    • Profile
      • get_profile
      • update_profile
      • list_auth_links
    • Query
      • `POST /api/query`.
    • Relationships
      • assert
      • fold
      • retype
      • reweight
    • Resources
      • `GET /api/resources` — **one response type, unconditionally.**
      • create_resource
      • get_resource
      • delete_resource
      • update_resource
      • List the finding at `{id}`'s citation-audit trail — one row per audit, each naming its auditor.
      • Record an auditor's signed defensibility verdict on one `(block, source)` citation of the finding at `{id}`. CONFORM to `handlers::edges::assert` (the sibling authored-write handler): thin — build the command, dispatch it, map the error. No persistence he
      • get_content
      • list_resource_edges
      • resource_evidence
      • grant_resource_access
      • revoke_resource_access
      • resource_lineage
      • provenance
      • annotate_resource
    • Meta
      • get_meta
      • update_meta
    • Reassign
      • reassign_resource
      • reassign_team
    • Search
      • `POST /api/search`.
    • Steward
      • candidates
      • dispatch
      • sweep
      • delta
      • advance
    • Teams
      • list_teams
      • create_team
      • detail
      • delete_team
      • update_team
      • add_member
      • remove_member
      • change_role
    • Schemas
      • AcceptInvitationResponse
      • ActInput
      • ActInvocation
      • ActName
      • ActQuantity
      • ActRefusal
      • ActorAuthority
      • AddMemberRequest
      • AdvanceWatermarkAck
      • AdvanceWatermarkRequest
      • AgentAuthorship
      • AppendBlockPayload
      • AssertRelationshipRequest
      • AtlasEdge
      • AtlasHome
      • AtlasNode
      • AtlasSubgraph
      • AuditCitation
      • AuditSweepRow
      • AuditorDispatchTickRequest
      • AuditorDispatchTickResponse
      • AuditorJobCompleteAck
      • BindTeamOutcome
      • BindTeamRequest
      • BlockId
      • BlockProvenanceRow
      • BlocksResponse
      • BodyStorage
      • Bridge
      • BulkReassignAck
      • BulkReassignRequest
      • ChangeRoleRequest
      • CharterBlock
      • CharterDisposition
      • CitationAuditRequest
      • CitationAuditRow
      • ClaimedAuditJob
      • ClaimedJob
      • CloseInvocationRequest
      • CogmapAnalyticsRow
      • CogmapDetail
      • CogmapFoundationRow
      • CogmapGrantBody
      • CogmapId
      • CogmapRegionMetricsRow
      • CogmapRegionRow
      • CogmapRegulationRow
      • CogmapRevokeBody
      • CogmapRow
      • CogmapStaleness
      • CombineNode
      • CombineOp
      • Composition
      • CompositionTrace
      • ConfidenceBand
      • ContentResponse
      • ContextCreateRequest
      • ContextId
      • ContextOwnerRef
      • ContextPanorama
      • ContextRow
      • ContextRowWithCounts
      • CorrelationId
      • CreateCogmapOutcome
      • CreateCogmapRequest
      • CreateInvitationRequest
      • CreateRequestBody
      • CreateReviewBody
      • DeleteResponse
      • DeviceOverrides
      • DispatchTickRequest
      • DispatchTickResponse
      • Disposition
      • DriftSweepRow
      • EdgeFacetRow
      • EdgeFacetSetRequest
      • EdgeFacetsResponse
      • EdgeFilter
      • EdgeId
      • EdgeKind
      • ElementEvent
      • ElementKind
      • EmbedDispatchSummary
      • Entitlements
      • ErrorBody
      • ErrorDetail
      • ErrorDetails
      • EventCursorResponse
      • EventTrail
      • ExactArm
      • ExactHit
      • Extent
      • FacetAck
      • FacetPredicate
      • FacetSetRequest
      • FinalizePayload
      • FoldRelationshipRequest
      • GrantOutcome
      • GraphEdgeRow
      • GroupKeyMeta
      • HealthResponse
      • HomeCogmap
      • HomeContext
      • IdKind
      • IdProvenance
      • IdSet
      • IdpRevocation
      • IngestCreateResponse
      • IngestDelta
      • IngestPayload
      • IngestState
      • InheritedReadGrant
      • InheritedShare
      • InputSource
      • Intention
      • InvitationStatus
      • InvitationTokenRequest
      • InviteeInvitation
      • InvocationAck
      • InvocationActRow
      • InvocationCloseAck
      • InvocationId
      • InvocationSummary
      • InvocationView
      • JoinRequest
      • JoinRequestStatus
      • LensId
      • LineageNode
      • ManagedMeta
      • MatchLocation
      • MaterializeAck
      • MaterializeDelta
      • MaterializeRequest
      • MergePolicy
      • MetaUpdatePayload
      • NarrowedBy
      • NodeHome
      • OpenInvocationRequest
      • OrdOp
      • OrphanNode
      • OutcomeDeclaration
      • PlanRefusal
      • PlanRefusalDetails
      • Polarity
      • Profile
      • ProfileAuthLink
      • ProfileId
      • ProfileUpdateRequest
      • ProfileWithEntitlements
      • PropertyOp
      • PropertyPredicate
      • ProvenanceSource
      • PublicSystemSettings
      • QuantityScale
      • QueryResponse
      • ReassignAck
      • ReassignContextOutcome
      • ReassignContextRequest
      • ReassignResourceRequest
      • ReconcileCogmapRequest
      • ReconcileEdge
      • ReconcileEdgeTombstone
      • ReconcileEntry
      • ReconcileOutcome
      • ReconcileTelos
      • ReconcileTelosBlock
      • ReconcileTombstone
      • Refusal
      • RefusalReason
      • RegionHit
      • RegionId
      • RelationshipAck
      • RemoveMemberOutcome
      • RenameContextOutcome
      • RenameContextRequest
      • ResidualBucket
      • ResidualContext
      • ResidualGroups
      • ResidualOwnedReach
      • ResourceAnnotateRequest
      • ResourceCreateRequest
      • ResourceFacetRow
      • ResourceFacets
      • ResourceFacetsResponse
      • ResourceFilter
      • ResourceGrantBody
      • ResourceHit
      • ResourceId
      • ResourceLineage
      • ResourceListResponse
      • ResourceRevokeBody
      • ResourceSection
      • ResourceSortField
      • ResourceUpdateRequest
      • ResourceView
      • ReturnSpec
      • RetypeRelationshipRequest
      • RevokeOutcome
      • ReweightRelationshipRequest
      • ScoreKind
      • Scoring
      • SearchParams
      • SearchReason
      • SearchResponse
      • SearchResultRow
      • SearchScope
      • SearchScopeInfo
      • SegmentInfo
      • SegmentedBegin
      • SegmentedBeginResponse
      • ShareContextOutcome
      • ShareContextRequest
      • SlackDisconnectRequest
      • SlackDisconnectResponse
      • SlackDisconnectedPrincipal
      • SliceRequest
      • SortOrder
      • StageDisposition
      • StageInput
      • StageInputTrace
      • StageName
      • StageNode
      • StageOutput
      • StageRelation
      • StageResult
      • StageTrace
      • Standing
      • StandingShape
      • Subscription
      • SubscriptionOverride
      • SystemAccessDetails
      • TeamCreateRequest
      • TeamDetail
      • TeamInvitation
      • TeamMemberDetail
      • TeamMemberRow
      • TeamMemberSource
      • TeamRole
      • TeamRow
      • TeamUpdateRequest
      • Territory
      • TerritoryKind
      • TerritoryOverview
      • UnbindTeamOutcome
      • UnshareContextOutcome
      • VaultConfig
      • ViaEntry
      • WideArm
      • WideHit
  1. auth

Temper auth & security

Canonical home for how Temper authenticates callers and authorizes what they can do.
If you are changing anything on an auth path — a new gate, a new token shape, a new
issuer, the reconcile channel — the explanation lives here, and this is where the
"did I touch both surfaces?" discipline is written down.

The one thing to remember#

Authorization is enforced on two surfaces — temper-api (HTTP middleware) and
temper-mcp (per-tool) — but the gate sequence lives once, in the
temper-services::auth seam. A gate added to the seam is enforced on both surfaces; a
gate hand-added to one surface's middleware silently misses the other.
This is not hypothetical. SAML Phase 2 added the is_active deactivation gate to
temper-api only; a deactivated account's valid token kept full MCP tool access until
the 2026-07-02 review caught it. The seam exists so the next gate cannot repeat that.
And the corollary, added when the seam took over principal construction:
the seam owns the principal. A surface hands in a verified token and gets back an
AuthenticatedProfile. It never builds an AuthClaims. If a surface can construct a
principal, it can construct a different principal than its sibling would — which is
exactly how the drift above keeps recurring one level down.

The surfaces and the seam#

  temper-api (HTTP)                          temper-mcp (rmcp tool call / initialize)
  ───────────────────                        ────────────────────────────────────────
  require_auth (middleware/auth.rs)          require_mcp_auth (middleware.rs)
    · verify JWT (JwksKeyStore)                · verify JWT (same JwksKeyStore)
    · aud = config.auth.audience               · aud = config.auth.audience (the SAME one)
    ↓ decode → RawJwtClaims                    ↓ inject RawJwtClaims + BearerToken
        │                                      ensure_profile_from_parts (service.rs)
        │                                          │
        └──────────────┐          ┌────────────────┘
                       ▼          ▼
             temper-services::auth  (the seam — sequence lives here once)
               authenticate_token(&state, &raw, token)   → AuthenticatedProfile [Level 1]
                 · classify → Machine | Human | Refuse         (normalize.rs)
                 · human: the email ladder                     (email.rs)
                 · build AuthClaims — the ONLY constructor
                 · resolve profile + `is_active` gate          (authenticate, pub(crate))
               require_system_access(pool, &authed)      → SystemAuthorized     [Level 2]
                       │          │
        ┌──────────────┘          └────────────────┐
        ▼                                            ▼
  map AuthzError → HTTP status                map AuthzError → rmcp ErrorData
  (401 / SystemAccessRequired)                (INVALID_REQUEST, terminal)
Each surface owns only two things the seam does not: (1) JWT signature verification
(the audience differs legitimately per surface) and its decode into the shared
RawJwtClaims, and (2) mapping AuthzError to its transport's words-on-the-wire.
Everything between — classify the token, run the human email ladder, construct the
AuthClaims, resolve the profile, gate on is_active, gate on system_access — is the
seam.
There is a third seam entry point, off the token path: resolve_federated_human, for
an identity a trusted peer already authenticated out-of-band (the SAML
reconcile channel — HMAC, no JWT, nothing to classify).

The two-level chain#

A future gate belongs to exactly one of these levels. "Add a gate = edit one
function" holds per level.
LevelFunctionGate it addsRuns on
1 — Authenticatedauthenticate_token (public) → authenticate (pub(crate))classify + email ladder + resolve profile + is_activeevery authed route/tool, both surfaces
2 — System-authorizedrequire_system_accesshas_system_access (gating-team membership)the gated tier of both surfaces
Level 2 is a typestate chain: require_system_access only accepts an
AuthenticatedProfile (produced solely by authenticate, which is reachable only through
authenticate_token) and returns SystemAuthorized. The compiler makes it impossible to
run Level 2 without having passed Level 1.
Why two levels and not one monolithic authorize(): temper-api splits into two
router tiers. The auth-only tier (view own profile, request access, team join) runs
Level 1 but deliberately skips Level 2 — that is how a not-yet-approved user requests
access in the first place. The gated tier adds Level 2. A single always-run-all-gates
function would break the request-access flow. (temper-mcp has no auth-only tier — every
tool requires Level 2.) See authorization-seam.md.

Checklist: changing an auth path#

New gate? Add it to the seam (crates/temper-services/src/auth/mod.rs) at the
correct level — not to a surface's middleware. Both surfaces pick it up for free.
Building an AuthClaims? Don't. Only the seam constructs a principal; the
constructor is authenticate_token (token path) or resolve_federated_human
(federated path). If you need a third path, add it inside the seam.
New AuthzError variant? Map it in both transport mappers:
temper-api middleware/auth.rs (Level 1) + middleware/system_access.rs
(Level 2), and temper-mcp service.rs::map_authz_error. The compiler's
exhaustiveness check enforces this. There are six variants today.
New token shape / issuer? See jwt-verification.md and
the machine-token contract — one claim contract both
issuers conform to; the Rust seam normalizes exactly one machine shape.
Ran the parity e2e? tests/e2e/tests/auth_seam_parity_e2e.rs drives the
production caller on both surfaces; tests/e2e/tests/auth_seam_m2m_e2e.rs does the
same for machine tokens through the real MCP gate. A direct-call unit test passes
even if a surface forgot to wire the seam — the e2e is what proves the wiring.

Documents in this area#

authorization-seam.md — the three public entry points, the
two-level chain, why the seam owns principal construction, AuthzError and its per-surface
transport mapping, the typestate, the router-tier split, and the parity test.
cognitive-map-authoring.md — the per-resource axis:
who may author into a cogmap or modify a resource. The three predicates
(cogmap_authorable_by_profile / can_modify_resource / anchor_readable_by_profile),
the full per-op gate map, agent-vs-human principals, and the container-write cascade
(the F1–F3 hardening findings are all shipped; they are recorded there for provenance).
jwt-verification.md — JwksKeyStore, RS256 (Auth0/OIDC) and
EdDSA (the SAML Authorization Server), the per-surface audience split, and what the
surface hands the seam (RawJwtClaims + the raw bearer).
reconcile-channel.md — the internal SAML reconcile channel,
its shared-secret/HMAC trust model, why not an origin allow-list on Vercel, and the
bounded blast radius.
machine-token-contract.md — the issuer /
resource-server boundary and the single machine-token claim contract both issuers
conform to (M2M agent principals; Auth0 and the Temper AS both mint them). Includes the
token-request wire shape, the end-to-end flow, and the operator runbook.

Related, elsewhere#

Operator setup for SAML SSO (env, keys, IdP row, group mappings):
../guides/self-hosting-saml.md. That guide is the
runbook; this area is the security model it implements.
Operator/integrator guide for machine principals (mint, reach, rotate, revoke):
../guides/machine-credentials.md. Same split: that
guide is how to run one, machine-token-contract.md is
what the code guarantees about one.
Design spec the seam was built from:
internal/superpowers/specs/2026-07-02-shared-auth-orchestration-seam-design.md.
Modified at 2026-08-19 17:59:52
Previous
Temper documentation
Next
The authorization seam (`temper-services::auth`)
Built with