1. guides
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. guides

Enterprise Install — Ground Up

This is the spine for a first real enterprise install — it flattens the phase guides into
one sequence. Each detailed step links to its phase guide; this document is the order and
the joins, not the detail.
Primary path: Temper's native Authorization Server fronting your Okta SAML app (see
self-hosting-saml.md). Auth0 and Okta-OAuth are noted variants —
see self-hosting.md and self-hosting-okta.md
if your organization uses one of those instead.

What you end up with#

OutcomeProduced by
Deployed API + MCP behind Okta-SAML SSOself-hosting.md deploy + self-hosting-saml.md
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.md#deploy-the-ui-optional
(deferred) The Eve stewardvercel-eve.md

Four phases#

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

Prerequisites#

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 (see
org-bootstrap.md § Prerequisites).
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.

Environment matrix#

One consolidated table across all three surfaces, sourced from the phase guides — this section
does not restate their prose, only where each variable lives and which other variables it must
match. 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 runbook (see Four phases § D and vercel-eve.md).
Sources: self-hosting.md § Environment variable contract,
self-hosting.md § Environment variable contract (UI project),
self-hosting-saml.md § 4 Environment variables,
packages/temper-ui/.env.example,
vercel-eve.md § Environment contract.
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). See slack-setup.md
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 self-hosting.md)
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 runbook)
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. See machine-credentials.md
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.md.
Self-hosters on the SAML-primary path should set OIDC_* directly.
² OIDC_DISCOVERY_URL is not part of this document's source A2 variable enumeration but is
required for the UI on the SAML-AS path (self-hosting-saml.md § 6, .env.example) — added here
because omitting it would misconfigure this guide's primary (SAML) path.
³ 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.md § Provision Neon
2Register Okta SAML app; capture cert / SSO URL / entity ids / group attribute statementmanualself-hosting-saml.md + Okta SAML app note 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-saml.md
4Set Vercel env (matrix + emitted bundle) on api + mcpmanualEnvironment matrix
5Deploy backend; sqlx migrate run against DATABASE_URL_UNPOOLEDmanualself-hosting.md § Run migrations
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-saml.md
7First admin signs in via SAML → JIT kb_profiles rowmanualself-hosting-saml.md
8SQL root step: gating team + first admin; VERIFY is_system_admin(<uuid>) = truesystem-bootstrap.sh --run-rootorg-bootstrap.md § 0
9temper admin settings (instance name, gating team, mode)system-bootstrap.shorg-bootstrap.md § 1
10temper team create everyone --auto-join-role watchersystem-bootstrap.shorg-bootstrap.md § 2
11temper admin saml map-group (after teams exist)saml-setup.sh (emit / --apply-db)self-hosting-saml.md
12temper admin saml verifysaml-setup.shself-hosting-saml.md
13Telos-charter: temper cogmap create → temper cogmap reconcile → bind +everyonesystem-bootstrap.shorg-bootstrap.md §§ 3–5
14(optional) UI deploy: confidential OIDC client, API_BASE_URL, SESSION_SECRETmanualself-hosting.md § Deploy the UI (optional)
15Verify: health, temper login, resource round-tripmanualself-hosting.md § Verify
—→ team-self-cognition + Eve steward: DEFERRED—vercel-eve.md
The expected path. The happy path is: run
scripts/bootstrap/saml-setup.sh --profile schema-artifact/saml-profile.yaml (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 --profile schema-artifact/saml-profile.yaml --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.md 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-saml.md, 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.
is_system_admin reads gating-team ownership, not system_access. It is true only when
the profile is an owner member of the team whose slug equals
kb_system_settings.gating_team_slug — kb_profiles.system_access = 'admin' does nothing for
it, and gating_team_slug is NULL by canonical-seed default, which denies everyone
(self-hosting-saml.md — a missing gating_team_slug "fails
silently with 403s"; l0-content-delivery.md § The gotcha
spells out both halves; confirmed in the field 2026-07-02). Set both halves at step 8 — the
gating team and the owner membership — and verify with is_system_admin(<uuid>) = true before
moving on, not just that the SQL ran.
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.md).
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 (AS deployment,
group provisioning).
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 (org-bootstrap.md).
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.md, DEPLOYING.md).

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 --profile schema-artifact/saml-profile.yaml (--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 runbook — 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
(vercel-eve.md § Status).
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 (org-bootstrap.md).
SCIM (Phase 3). Group provisioning today is JIT on login; immediate deprovisioning needs
SCIM, not yet available (self-hosting-saml.md § 3).
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 runbook's
is_system_admin gate covers the L0 kernel only, not team-scoped cogmaps.
Modified at 2026-08-19 17:59:52
Previous
Drain operator queries (TraceQL)
Next
Operator guide: read-only GitHub credential via a BYO App + Vercel Connect
Built with