1. playbooks
temper
  • Temper documentation
  • cli
    • README
    • admin
    • auth
    • check
    • cogmap
    • config
    • context
    • edge
    • init
    • invitations
    • invocation
    • memory
    • pull
    • query
    • resource
    • search
    • skill
    • slack
    • status
    • steward
    • team
    • trail
    • update
    • version
    • warmup
  • config
    • README
  • auth
  • guides
  • doors
    • Building against Temper
    • Running Temper
    • Using Temper
  • concepts
    • Auth Identity
    • Authoring Authorization
    • Contexts and Refs
    • Machine Tokens
    • Operational Memory
    • Release Verification
    • The SAML Reconcile Channel
    • Slack Identity and Revocation
    • Teams and Roles
    • Telemetry
    • Token Verification
    • The Trust Boundary
  • playbooks
    • Adopt operational memory
    • Authenticate
    • Bootstrap an Org
    • Bootstrap a Team's Self-Cognition
    • Build a cognitive map
    • Connect Claude Code
    • Connect Temper to Claude Desktop
    • Deliver L0 Kernel Content
    • Deploy a Steward Agent
    • Deploy the Web UI
    • Enterprise Install
    • Provision a Read-Only GitHub Connection
    • Ingest a corpus
    • Install Temper
    • Run a Team
    • Self-Host Temper
    • Self-host Temper with Okta
    • Self-host Temper with a SAML IdP
    • Send traces to an OTLP backend
    • Slack Mentions
    • Standing Up a Machine Credential
  • sdks
    • Integrating Ruby with temper-rb
  • 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
  • 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
  1. playbooks

Enterprise Install

For operators — someone doing a ground-up enterprise install. This is an annotated
variant of Self-hosting Temper, not a standalone guide. The base
playbook covers the core API + MCP deployment; this one adds the enterprise-specific
configuration on top: SAML SSO, group provisioning, and org bootstrap. Start with the base
playbook for the deploy, then follow this one for the enterprise extensions.
By the end you will have a deployed Temper instance behind Okta SAML SSO, with a first
system admin, instance settings, an everyone-team every member auto-joins, and an
org-identity cognitive map — ready for your users to sign in and work.
Primary path: Temper's native Authorization Server fronting your Okta SAML app (see
Self-hosting with SAML). Auth0 and Okta-OAuth are noted
variants — see Self-hosting Temper and
Self-hosting with Okta if your organization uses one of those
instead.

Prerequisites#

The base deployment stood up. This playbook assumes you have followed
Self-hosting Temper and have the API + MCP surfaces deployed.
An embed-capable temper binary. Org bootstrap's cogmap create / cogmap reconcile
embed the charter client-side (ONNX). The default install bundles it; if you built from
source, reinstall with cargo install --path crates/temper-cli --locked --force.
psql and DATABASE_URL_UNPOOLED for the DB-only steps — running migrations and the
irreducible SQL root step that promotes the first system admin.
Okta admin access to create the SAML app and configure the AS.
A Vercel project to host the API + MCP surfaces (and, optionally, a second project for
the web UI).
A Neon project (PostgreSQL 17) for the instance database.
Familiarity with the auth-identity contract — see
Auth Identity for the issuer/audience agreement rules the
server enforces at boot.
Familiarity with the trust boundary — see
The Trust Boundary for the two-gate access model every
call crosses.

What you end up with#

OutcomeProduced by
Deployed API + MCP behind Okta-SAML SSOSelf-hosting Temper deploy + Self-hosting with SAML
A first system adminthe SQL root step (irreducible)
Instance settings (name, gating, mode)temper admin settings
An everyone-team every member auto-joinstemper team create … --auto-join-role watcher
An org-identity telos-charter cognitive map, born + boundtemper cogmap create → temper cogmap reconcile → temper cogmap bind
(optional) The web UISelf-hosting Temper
(deferred) The Eve stewarddeferred — not sequenced in this playbook

Four phases#

(A) Install the temper binary — a prerequisite for every phase below; see
Install Temper.
(B) Backend deploy + auth — stand up the API + MCP surfaces on Vercel + Neon, wired to
Okta SAML. See Self-hosting Temper and
Self-hosting with SAML.
(C) Org bootstrap — take the blank-but-stable install to a usable org: first admin,
instance settings, everyone-team, org-identity cognitive map. See
Bootstrap an Org.
(D) Agents [deferred] — deploying an Eve agent (the team-self-cognition steward) against
the instance. Not sequenced in this playbook.

Environment matrix#

One consolidated table across all three surfaces. Set the api+mcp and SAML-AS rows
before Phase B; the temper-ui rows only if you deploy the optional UI. The eve column
is deferred — surfaced for completeness, not a step in this playbook.
Sources: Self-hosting Temper,
Self-hosting with SAML.
Variabletemper-cloud (api+mcp)temper-uieve (deferred)Notes
Database
DATABASE_URLYes (pooled, runtime)Yes (same pooled string; read-only nav chrome)—One Neon connection string shared by api/mcp/ui
DATABASE_URL_UNPOOLEDYes (deploy step only)——Direct Neon connection string; migrations only
Auth (issuer / audience / provider)
AUTH_ISSUERYes——Auth0 tenant, or AS_ISSUER value in the SAML path
JWKS_URLYes——Auth0 JWKS, or https://<instance>/oauth/jwks in the SAML path
AUTH_AUDIENCEYes——Must equal AS_AUDIENCE / MCP_AUDIENCE / UI OIDC_AUDIENCE
AUTH_PROVIDER_NAMEYes——auth0, or saml:<idp-key> in the SAML path (max 32 chars)
MCP_AUDIENCENo——Optional. An instance has ONE audience; both surfaces read AUTH_AUDIENCE. If set, must equal it — enforced at boot, not by discipline.
MCP_CLIENT_IDYes——Auth0 MCP native app client_id; n/a in the SAML path (client allowlisting is AS_CLIENTS instead)
MCP_BASE_URLYes——https://<instance> — used in OAuth discovery responses
SAML Authorization Server (AS) block
AS_ISSUERYes (SAML path)——Setting this flips the instance into AS mode
AS_AUDIENCEYes (SAML path)——Must equal AUTH_AUDIENCE
AS_SIGNING_KEY_PKCS8Yes (SAML path)——Ed25519 signing key, PKCS#8 PEM — secret
AS_SIGNING_KIDYes (SAML path)——Key id published in the JWKS
AS_CLIENTSYes (SAML path)——JSON client_id → [redirect_uris] allowlist; unset = fail-closed
AS_ACCESS_TTL_SECONDSOptional (default 900)——Access-token lifetime
AS_REFRESH_TTL_SECONDSOptional (default 2592000, 30d)——Refresh-token lifetime
Group provisioning / reconcile channel (SAML Phase 2)
INTERNAL_RECONCILE_SECRETYes (SAML path; shared AS+API)——Same value on both; unset disables reconcile (auth still works)
INTERNAL_RECONCILE_URLYes (SAML path; AS side)——Full URL of the API's /internal/saml/reconcile
Slack account link (optional; needed only to run the @temper mention agent)
SLACK_LINK_CLIENT_IDYes (Slack path)——OAuth client the link flow authorizes as. Auth0: a native/PKCE app's client_id. SAML path: a client_id present in AS_CLIENTS
SLACK_LINK_SECRETYes (Slack path; shared API+agent)——Shared secret gating /internal/slack/link-state; same value on the mention agent. Unset ⇒ the endpoint is disabled (auth still works)
PUBLIC_BASE_URLYes (Slack path)——https://<instance> — the origin the link redirect_uri is built from³. All four unset together is the supported "no Slack" state
SLACK_VAULT_ENC_KEYYes (Slack path)——32-byte base64 AEAD key (openssl rand -base64 32) encrypting each stored per-user refresh token. Malformed ⇒ the whole Slack flow disables. Rotation is flag-day today (users re-link)
Storage / build
BLOB_READ_WRITE_TOKENYes——Vercel Blob token for the upload/extract/embed pipeline
SQLX_OFFLINEYes (build)——Must be true
Optional / situational (api+mcp)
ENABLE_SWAGGEROptional——Exposes /swagger-ui in non-production
PORTOptional——Platform-injected by Vercel
CORS_ORIGINSSituational——Only for a separate cross-origin browser client — the bundled UI same-origin-proxies and does not need it
UI connectivity
API_BASE_URL—Yes—The API's own origin, not the UI's public origin (loop-detection warning in the base playbook)
APP_URL—Yes—The UI's own public origin
UI OIDC client
OIDC_ISSUER—Yes¹—Must resolve the same issuer as AUTH_ISSUER / AS_ISSUER
OIDC_CLIENT_ID—Yes¹—temper-ui in the SAML AS path
OIDC_CLIENT_SECRET—Yes¹ (omit in the SAML AS path)—The AS registers temper-ui as a public PKCE client — no secret
OIDC_AUDIENCE—Situational—Required for Auth0; omit for Okta custom AS / the SAML AS (carried implicitly)
OIDC_PUBLIC_CLIENT—Yes (SAML AS path)—Declares the secret-less PKCE path; without it the UI fails fast at startup
OIDC_DISCOVERY_URL²—Yes (SAML AS path)—Points the UI at the AS's RFC 8414 metadata — the AS has no /.well-known/openid-configuration
Session / storefront
SESSION_SECRET—Yes—≥32 bytes of entropy (64-char hex or 44-char base64)
STOREFRONT_ENABLED—Optional—Set falsy to disable the public marketing route group on app-only installs
Eve (DEFERRED — not a step in this playbook)
TEMPER_MCP_URL——YesThe temper-mcp endpoint, e.g. https://<instance>/mcp
TEMPER_API_URL——YesThe temper REST base, e.g. https://<instance>
TEMPER_M2M_CLIENT_ID——ProductionThe agent's machine-principal client id. Register it first — authentication is fail-closed, with no just-in-time create. On this (AS-mode) install, mint it with temper admin machine issue, which returns a tmpr_… id
TEMPER_M2M_CLIENT_SECRET——ProductionThe one-time secret from issue. Never in code; rotate with rotate-secret
TEMPER_M2M_TOKEN_URL——ProductionThe issuer's token endpoint. On this install that is your own instance's /oauth/token — Temper is the Authorization Server (AS_ISSUER set)
TEMPER_M2M_AUDIENCE——External IdP onlyOmit it here. Auth0 requires an audience; Temper's own AS ignores a request-supplied one entirely and mints with AS_AUDIENCE
TEMPER_CONNECT_CONNECTOR——FallbackVercel Connect connector id. Used only when TEMPER_M2M_CLIENT_ID is unset, and it cannot mint an app token against an Auth0-fronted instance
TEMPER_TOKEN——Dev onlyPre-obtained token; drives eve dev. Not for production
STEWARD_MODEL——OptionalThe agent's primary model (default minimax/minimax-m3). Resolved at build time, so a change needs a redeploy; an unknown id fails the build
STEWARD_MODEL_FALLBACKS——OptionalComma-separated, tried in order after the primary fails. Covers availability (5xx, rate limit), never quality
¹ Back-compat fallback: if OIDC_* are unset, the UI falls back to the canonical deployment's
AUTH0_* variables — see Self-hosting Temper. Self-hosters on the
SAML-primary path should set OIDC_* directly.
² OIDC_DISCOVERY_URL is required for the UI on the SAML-AS path — the AS has no
/.well-known/openid-configuration, so the UI needs the explicit discovery URL.
³ The link client's redirect_uri must be registered, or the IdP refuses the authorize request
before temper ever sees it.
The flow derives it as <PUBLIC_BASE_URL>/api/auth/slack/callback.
On this (AS-mode) install, add that exact URL to SLACK_LINK_CLIENT_ID's entry in AS_CLIENTS
— unset AS_CLIENTS is fail-closed. On an Auth0-fronted install, add it to the application's
Allowed Callback URLs. It is an exact-match allowlist on both: a trailing slash or an http://
scheme is a different URL.

Must-match by construction#

The audience and issuer joins are enforced at boot. Temper parses them once and refuses to
start
if they disagree, naming the offending variable and the relation it must satisfy. You do
not have to hold this table in your head — but the values below are what it checks, and
JWKS_URL must be $AS_ISSUER/oauth/jwks on an AS instance.
JoinValues that must be equal
AudienceAS_AUDIENCE = AUTH_AUDIENCE = MCP_AUDIENCE (if set) = UI OIDC_AUDIENCE
IssuerAS_ISSUER = AUTH_ISSUER; UI OIDC_ISSUER resolves the same issuer
Provider labelAUTH_PROVIDER_NAME = saml:<idp-key>
Reconcile secretINTERNAL_RECONCILE_SECRET identical on the AS and API env (same Vercel project)
DatabaseDATABASE_URL (pooled) shared api/mcp/ui; DATABASE_URL_UNPOOLED migrations only
temper admin saml provision renders the AS_* + reconcile block so these are consistent by
construction — it is the reason the SAML env is emitted, not hand-written.

The timeline#

temper admin saml provision is an inert emitter — it never touches a running instance. It
runs early (step 3, before the deploy) only because it generates the Ed25519 AS signing key and
the INTERNAL_RECONCILE_SECRET that must already be in the env when the backend deploys. Emitting
early does not mean applying early: provision produces two artifacts that land at different
points in the timeline. The env bundle (--env-out) is consumed pre-deploy, at step 4 (Vercel
env). The kb_saml_idp INSERT (--sql-out) can only be applied post-migrate, at step 6 —
kb_saml_idp is a table created by the migrations run at step 5, so applying it any earlier is
impossible, not just out of order.
#StepOwnerDetail link
1Provision Neon (PG17, vector + pg_uuidv7, pooled/unpooled)manualSelf-hosting Temper
2Register Okta SAML app; capture cert / SSO URL / entity ids / group attribute statementmanualSelf-hosting with SAML + Okta SAML app below
3temper admin saml provision → generate keys, --env-out bundle, --sql-out kb_saml_idp SQL (inert; early for the env keys)saml-setup.sh (emit)Self-hosting with SAML
4Set Vercel env (matrix + emitted bundle) on api + mcpmanualEnvironment matrix
5Deploy backend; sqlx migrate run against DATABASE_URL_UNPOOLEDmanualSelf-hosting Temper
6Apply the kb_saml_idp row (saml-setup.sh --apply-db, or psql the --sql-out file by hand)saml-setup.sh (--apply-db)Self-hosting with SAML
7First admin signs in via SAML → JIT kb_profiles rowmanualSelf-hosting with SAML
8SQL root step: gating team + first admin; VERIFY has_system_access(<uuid>) AND is_system_admin(<uuid>) both truesystem-bootstrap.sh --run-rootBootstrap an Org
9temper admin settings (instance name, gating team)system-bootstrap.shBootstrap an Org
10temper team create everyone --auto-join-role watchersystem-bootstrap.shBootstrap an Org
11temper admin saml map-group (after teams exist)saml-setup.sh (emit / --apply-db)Self-hosting with SAML
12temper admin saml verifysaml-setup.shSelf-hosting with SAML
13Telos-charter: temper cogmap create → temper cogmap reconcile → bind +everyonesystem-bootstrap.shBootstrap an Org
14(optional) UI deploy: confidential OIDC client, API_BASE_URL, SESSION_SECRETmanualSelf-hosting Temper
15Verify: health, temper auth login, resource round-tripmanualSelf-hosting Temper
—→ team-self-cognition + Eve steward: DEFERRED—deferred
The expected path. The happy path is: run saml-setup.sh (step 3, default emit — writes the
env bundle consumed at step 4 and holds the kb_saml_idp SQL for step 6), do the two platform
steps by hand (4–5, Vercel env + deploy/migrate), then run system-bootstrap.sh --run-root
(steps 8–10 and 13) and re-run saml-setup.sh --apply-db (steps 6, 11, and 12 — applies the
kb_saml_idp row, maps the now-existing teams' IdP groups, and verifies against the live DB).
The numbered breakdown above is the reference an operator reads to understand what each script
does, or falls back to when running by hand. The two scripts are kept separate so
system-bootstrap.sh (steps 8–10, 13) works unchanged for Auth0/Okta-OAuth installs, which swap
steps 2–3, 6, and 11–12 for the Auth0 app registration documented in
Self-hosting Temper instead.

Okta SAML app#

In Okta, create a SAML 2.0 app and capture four values off it:
the SSO URL → idp_sso_url / --idp-sso-url
the signing certificate (PEM) → idp_cert_file / --idp-cert-file
the IdP entity id → idp_entity_id / --idp-entity-id
a group attribute statement exposing the user's groups → groups_attr / --groups-attr
(e.g. groups)
This note covers only what to pull out of Okta's app screen. The generic SAML-IdP side — the SP
ACS URL and entity id Temper's AS expects the IdP to send assertions to — is documented in
Self-hosting with SAML, and is the same regardless of which IdP you use.

Traps#

Five ways this install silently misbehaves instead of failing loudly. Each has bitten a real
install; read this before step 8.
Admission and admin-ness are two independent rows, and each reads exactly one table.
has_system_access reads kb_principal_standing (an approved row); is_system_admin reads
kb_principal_governance. Neither reads team membership, and a fresh instance has neither row,
so it denies everyone — silently, as 403s rather than a startup failure. Write both rows
at step 8
and verify both predicates — has_system_access(<uuid>) and
is_system_admin(<uuid>) each returning true — before moving on, not just that the SQL ran.
One without the other is a silent half-state.
API_BASE_URL pointed at the UI's own public origin creates a self-proxy loop → 508 Loop Detected. It must be the API backend's own distinct origin — its *.vercel.app URL or a
dedicated api. subdomain — never the shared public domain the UI also serves
(Self-hosting Temper).
AS_CLIENTS unset rejects every /oauth/authorize call (fail-closed); INTERNAL_RECONCILE_SECRET
unset silently disables group provisioning while auth still works.
The first fails loud, the
second doesn't — nothing errors, groups just never sync, so verify reconcile explicitly rather
than trusting a clean login (Self-hosting with SAML).
cogmap create / cogmap reconcile require an embed-feature temper binary. A
non-embed build fails with a clear requires the 'embed' feature error rather than a cryptic
one, but only at step 13, well after the rest of the install has succeeded — check this
up front instead (Bootstrap an Org).
Migrations are a deploy step, not a startup step — the API never auto-migrates. Run
sqlx migrate run against DATABASE_URL_UNPOOLED (step 5) yourself, and back up the database
first — there is no automatic rollback if a migration fails partway
(Self-hosting Temper).

Scripted vs. manual, and what's deferred#

The expected path is the two scripts, not the numbered table read step-by-step — the table
is the reference an operator falls back to when a script needs debugging or the install deviates
from the happy path (SAML variant swaps, a failed step to re-run by hand, etc.).
StepsAutomated byStatus
8–10, 13system-bootstrap.sh --run-rootExists today
3, 6, 11, 12saml-setup.sh (--apply-db for 6, 11, 12)Exists today
1–2, 4–5, 7, 14–15— (manual)Platform-console and human-in-the-loop steps: provisioning Neon and the Okta app, setting Vercel env, deploying, the first SAML login, and the optional UI deploy/verify — none of these are things a script can safely do on an operator's behalf
What's deferred beyond this playbook — the roadmap tail, not steps to sequence here:
Eve / machine-to-machine auth. The app principal needs client_credentials (M2M) support
that doesn't exist yet; until then Eve can't reach temper-mcp unattended.
plan/diff applier semantics. system-bootstrap.sh has no state backend — re-applying a
profile converges because every step is idempotent, but there's no Terraform-like plan/diff
preview (Bootstrap an Org).
SCIM (Phase 3). Group provisioning today is JIT on login; immediate deprovisioning needs
SCIM, not yet available (Self-hosting with SAML).
Cogmap-write-by-team-role. Authorial (write) RBAC for team contexts and team cognitive maps
is still undefined — de facto, any team member can write, not just admins/owners. This playbook's
is_system_admin gate covers the L0 kernel only, not team-scoped cogmaps.

Further reading#

The base deployment this playbook extends:
Self-hosting Temper.
The org bootstrap steps in detail:
Bootstrap an Org.
The auth-identity contract (issuer, audience, boot enforcement):
Auth Identity.
The trust boundary every call crosses:
The Trust Boundary.
SAML SSO configuration:
Self-hosting with SAML.
Okta OAuth configuration:
Self-hosting with Okta.
What the architecture fixes vs. what a deployment chooses:
temperkb.io/operating/deployment.
Modified at 2026-08-20 02:05:41
Previous
Deploy the Web UI
Next
Provision a Read-Only GitHub Connection
Built with