Status: Accepted protected-main diagrams with integrated changes labelled. Last reviewed: 2026-08-11
flowchart LR
USER[User / workforce identity]
EXT[External IdPs / LDAP / HR-IGA]
EDGE[WAF / public edge]
KC[Keycloak engine]
KCAPI[Keycloak Admin REST API]
ADMIN[Account-unification + SCIM API]
DEPLOY[Private deployment controller]
KV[(KV / secret manager)]
KCDB[(Keycloak-owned PostgreSQL)]
KVS[(Keyverse-owned config / intent / receipt / audit store)]
RP[CWL relying parties]
USER --> EDGE
EXT --> EDGE
EDGE --> KC
EDGE --> ADMIN
KC --> KCDB
KCAPI --> KC
ADMIN --> KCAPI
ADMIN --> KVS
DEPLOY --> KV
DEPLOY --> ADMIN
DEPLOY --> KCAPI
KC --> RP
The two storage nodes are authority boundaries, even when a deployment places them on the same physical database service. Keycloak owns and migrates its internal schema. Keyverse reads/writes only its own supported store and reaches Keycloak user/client/federation state through the supported Admin API rather than direct private-table access.
sequenceDiagram
actor Operator
participant Deploy as Deployment controller
participant Keyverse
participant Store as Keyverse state store
participant Keycloak as Keycloak Admin API/engine
Operator->>Deploy: private rendered federation payload
Deploy->>Keyverse: authenticated preflight
Keyverse->>Keyverse: local closed-schema validation
Keyverse-->>Deploy: redacted readiness result
Deploy->>Keyverse: desired-state apply/reconcile
Keyverse->>Store: persist versioned intent
Keyverse->>Keycloak: exact lookup / create or update
Keycloak-->>Keyverse: live remote state
Keyverse->>Keyverse: canonical re-observation
Keyverse->>Store: write desired-version-bound receipt
Keyverse-->>Deploy: redacted outcome
Deploy->>Operator: controlled acceptance evidence
sequenceDiagram
actor AppOwner
participant Deploy as Deployment controller
participant Keyverse
participant Store as Desired-state store
participant Keycloak as Keycloak Admin API/engine
participant Secret as Secret-management port
participant App as Relying party
AppOwner->>Deploy: secret-free client representation
Deploy->>Keyverse: preflight
Keyverse-->>Deploy: policy result
Deploy->>Keyverse: reconcile desired state
Keyverse->>Store: versioned intent
Keyverse->>Keycloak: exact client search/create/update
Keycloak-->>Keyverse: exact live representation
Keyverse->>Store: version-bound apply receipt
opt confidential client
Deploy->>Secret: provision secret separately
Secret-->>App: controlled credential placement
end
Deploy->>App: run login/logout/token acceptance
PR #72 extends this sequence with a closed mapper profile and is integrated in protected main; downstream authorization acceptance remains deployment specific.
Downstream authorization is a separate sequence after token issuance:
sequenceDiagram
participant Keycloak
participant RP as Non-fork RP
participant Policy as RP ABAC/RBAC policy
participant Resource as Tenant/resource store
Keycloak-->>RP: signed OIDC token
RP->>RP: validate issuer/signature/algorithm/exp/sub/aud
RP->>Policy: verified tenant, resource, purpose, role/scope
Policy->>Resource: same-tenant ownership and policy check
Resource-->>Policy: allow or deny
Policy-->>RP: authorization decision
Authentication, client reconciliation, and mapper presence do not bypass the RP policy sequence. ADR-0008 records the audited status of each non-fork RP.
stateDiagram-v2
[*] --> distinct_accounts
distinct_accounts --> candidate_link: exact subject / verified email / operator evidence
candidate_link --> rejected: unsafe or ambiguous evidence
candidate_link --> locked: acquire shared user-operation lock
locked --> merging
merging --> survivor_active
merging --> rollback_required: downstream/transaction failure
survivor_active --> duplicate_tombstoned
duplicate_tombstoned --> [*]
rollback_required --> distinct_accounts
rejected --> [*]
Unverified email cannot enter candidate_link by itself.
flowchart LR
PUT[SCIM full replacement PUT]
PATCH[SCIM PATCH active=false]
MERGE[Merge/link mutation]
LOCK[user_operation_lock_state]
USER[Keycloak user state]
AUDIT[account_merge_audit / operation evidence]
PUT --> LOCK
MERGE --> LOCK
LOCK --> USER
PATCH --> LOCK
USER --> AUDIT
Protected main guarantees the shared cross-process lock for merge/link and
full SCIM replacement. Active PR #113 extends the boundary to the supported
PATCH active=false deprovisioning path; its retryable SCIM 503 behavior is
not a protected-main guarantee until exact-head review and Checks pass.
flowchart LR
MODEL[OpenCode model process]
VERIFY[credential-free verifier]
PUB[bounded PR publisher]
REVIEW[independent review/security]
MAIN[protected main]
MODEL --> VERIFY
VERIFY --> PUB
PUB --> REVIEW
REVIEW --> MAIN
PR #74 is integrated in protected main and changes the exact hourly gate implementation without changing this authority separation; a protected-main scheduled or manual run remains operational evidence.
Update these views whenever Keycloak/Keyverse/deployment-controller ownership, identity matching, desired-state lifecycle, secret boundary, persistence, or protected automation authority changes. Active-PR items must not be relabelled as protected-main until integrated.