Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2644054
docs: add canonical documentation map
seonghobae Aug 9, 2026
d427218
docs: add canonical product requirements
seonghobae Aug 9, 2026
43d6002
docs: add canonical technical requirements
seonghobae Aug 9, 2026
084cef8
docs: add identity runtime UML views
seonghobae Aug 9, 2026
77d132d
docs: add identity control-plane ERD
seonghobae Aug 9, 2026
2a7a024
docs: add identity threat model
seonghobae Aug 9, 2026
64efc5a
docs: add identity test strategy
seonghobae Aug 9, 2026
57a13cd
docs: add identity operability guide
seonghobae Aug 9, 2026
34848dc
docs: add identity requirements traceability
seonghobae Aug 9, 2026
967303a
docs: add identity ADR index
seonghobae Aug 9, 2026
9c833b5
docs: record identity hub ADR
seonghobae Aug 9, 2026
b120c09
docs: record passwordless ADR
seonghobae Aug 9, 2026
54bb818
docs: record identity matching ADR
seonghobae Aug 9, 2026
9330856
docs: record reconciliation ADR
seonghobae Aug 9, 2026
e5c336d
docs: record configuration ownership ADR
seonghobae Aug 9, 2026
a5c3a8b
docs: record user-operation lock ADR
seonghobae Aug 9, 2026
1bf79ae
docs: record automation authority ADR
seonghobae Aug 9, 2026
ee38d88
test: enforce canonical identity documentation
seonghobae Aug 9, 2026
7ce6512
test: tighten identity documentation contracts
seonghobae Aug 9, 2026
c10d05a
docs: tighten active-PR and SCIM lock boundaries
seonghobae Aug 9, 2026
54edc0c
docs: scope shared user-operation lock to implemented paths
seonghobae Aug 9, 2026
f1c1569
docs: distinguish SCIM PUT and PATCH lock coverage
seonghobae Aug 9, 2026
e8fa0de
docs: define tenant and receipt identity constraints
seonghobae Aug 9, 2026
5b718b3
docs: use explicit research and operations terminology
seonghobae Aug 9, 2026
14dec35
docs: clarify Naruon and SCIM operating scope
seonghobae Aug 9, 2026
5987c8d
docs: separate Keycloak and Keyverse storage authority
seonghobae Aug 9, 2026
a6cf52c
docs: align identity traceability maturity and lock scope
seonghobae Aug 9, 2026
0a1d441
docs: align SCIM lock and RP redirect contracts
seonghobae Aug 9, 2026
4d28410
docs: name canonical research and runbook records
seonghobae Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Keyverse Documentation Map

Keyverse already has strong feature-specific specifications, doctoring, federation/onboarding, topology, and operations records. This index makes the cross-cutting product and architecture graph explicit without replacing those slice documents.

| Area | Canonical document |
|---|---|
| Product requirements | [`docs/PRD.md`](docs/PRD.md) |
| Technical requirements | [`docs/TRD.md`](docs/TRD.md) |
| Architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) |
| Topology | [`docs/topology.md`](docs/topology.md) |
| UML/runtime/authority flows | [`docs/UML.md`](docs/UML.md) |
| Logical/physical ERD | [`docs/ERD.md`](docs/ERD.md) |
| Threat model | [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) |
| Test strategy | [`docs/TEST_STRATEGY.md`](docs/TEST_STRATEGY.md) |
| Operability/recovery/release | [`docs/OPERABILITY.md`](docs/OPERABILITY.md) |
| Requirements/evidence traceability | [`docs/TRACEABILITY.md`](docs/TRACEABILITY.md) |
| Architecture decisions | [`docs/adr/README.md`](docs/adr/README.md) |
| Federation onboarding | [`docs/federation-onboarding.md`](docs/federation-onboarding.md) |
| RP onboarding | [`docs/rp-onboarding.md`](docs/rp-onboarding.md) |
| Account merge/unification | [`docs/merge-unification-flow.md`](docs/merge-unification-flow.md) |
| Standards/APA 7 evidence | [`docs/doctoring/`](docs/doctoring/) and [`docs/papers/`](docs/papers/) |
| Operations | [`docs/operations/`](docs/operations/) |
| Security reporting | [`SECURITY.md`](SECURITY.md) |
| Agent instructions | [`AGENTS.md`](AGENTS.md) |
| Agent context | [`CLAUDE.md`](CLAUDE.md) |
| Product overview | [`README.md`](README.md) |
| Change history | [`CHANGELOG.md`](CHANGELOG.md) |

## Maturity vocabulary

- **implemented-main** — present on protected main with source/tests.
- **active-PR** — implemented only on an open PR and not yet a protected-main claim.
- **deployment-owned** — private tenant/customer secret/configuration behavior owned by deployment controller/secret store.
- **external-system** — Keycloak/ADFS/LDAP/external OIDC/HR/IGA behavior not implemented by Keyverse itself.
- **planned** — accepted target without executable implementation.

Open PR #72 OIDC RP claim mapper profile and PR #74 hourly GitHub API remediation remain active-PR until merged. Keyverse's current protected-main desired-state/reconciliation capabilities are documented independently from those changes.
189 changes: 189 additions & 0 deletions docs/ERD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Keyverse Logical and Persistence ERD

**Status:** Accepted cross-cutting data model. Exact Keycloak internal schema remains Keycloak-owned.
**Last reviewed:** 2026-08-09

Keyverse persists its own configuration, desired-state, receipts, merge audit, and user-operation locks while Keycloak/PostgreSQL owns canonical IdP users/sessions/clients/federation runtime state. This ERD models Keyverse-owned durable records and their relation to external Keycloak identities without pretending to own Keycloak's internal tables.

```mermaid
erDiagram
IDP_CONFIG_ENTRY }o--|| TENANT_DEPLOYMENT : scoped_to
FEDERATION_SOURCE }o--|| TENANT_DEPLOYMENT : scoped_to
DIRECTORY_FEDERATION_SOURCE }o--|| TENANT_DEPLOYMENT : scoped_to
RELYING_PARTY_SOURCE }o--|| TENANT_DEPLOYMENT : scoped_to

FEDERATION_SOURCE ||--o{ FEDERATION_APPLY_RECEIPT : produces
DIRECTORY_FEDERATION_SOURCE ||--o{ DIRECTORY_FEDERATION_APPLY_RECEIPT : produces
RELYING_PARTY_SOURCE ||--o{ RELYING_PARTY_APPLY_RECEIPT : produces

KEYCLOAK_USER_REFERENCE ||--o{ ACCOUNT_MERGE_AUDIT : survivor_or_duplicate
KEYCLOAK_USER_REFERENCE ||--o| USER_OPERATION_LOCK_STATE : guarded_by
KEYCLOAK_USER_REFERENCE ||--o{ EXTERNAL_IDENTITY_LINK : owns
EXTERNAL_IDENTITY_LINK }o--|| FEDERATION_SOURCE : originates_from

TENANT_DEPLOYMENT {
uuid tenant_deployment_id PK
text deployment_name
text deployment_status_code
timestamptz created_at
}

IDP_CONFIG_ENTRY {
uuid idp_config_entry_id PK
uuid tenant_deployment_id FK
text config_key
text protected_value_ref
text config_version
timestamptz updated_at
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

FEDERATION_SOURCE {
uuid federation_source_id PK
uuid tenant_deployment_id FK
text federation_alias
text protocol_code
jsonb secret_free_desired_state
text desired_state_hash
text lifecycle_status_code
timestamptz updated_at
}

FEDERATION_APPLY_RECEIPT {
uuid federation_apply_receipt_id PK
uuid federation_source_id FK
uuid apply_attempt_id UK
text desired_state_hash
text keycloak_resource_id
text observed_state_hash
text apply_outcome_code
timestamptz observed_at
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

DIRECTORY_FEDERATION_SOURCE {
uuid directory_federation_source_id PK
uuid tenant_deployment_id FK
text directory_alias
jsonb private_desired_state
text desired_state_hash
text lifecycle_status_code
timestamptz updated_at
}

DIRECTORY_FEDERATION_APPLY_RECEIPT {
uuid directory_federation_apply_receipt_id PK
uuid directory_federation_source_id FK
uuid apply_attempt_id UK
text desired_state_hash
text keycloak_component_id
text observed_state_hash
text apply_outcome_code
timestamptz observed_at
}

RELYING_PARTY_SOURCE {
uuid relying_party_source_id PK
uuid tenant_deployment_id FK
text client_id
jsonb secret_free_desired_state
text desired_state_hash
text lifecycle_status_code
timestamptz updated_at
}

RELYING_PARTY_APPLY_RECEIPT {
uuid relying_party_apply_receipt_id PK
uuid relying_party_source_id FK
uuid apply_attempt_id UK
text desired_state_hash
text keycloak_client_uuid
text observed_state_hash
text apply_outcome_code
timestamptz observed_at
}

KEYCLOAK_USER_REFERENCE {
uuid keycloak_user_reference_id PK
uuid tenant_deployment_id FK
text keycloak_user_uuid
text lifecycle_status_code
}

EXTERNAL_IDENTITY_LINK {
uuid external_identity_link_id PK
uuid keycloak_user_reference_id FK
uuid federation_source_id FK
text external_subject_hash
boolean email_verified
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

ACCOUNT_MERGE_AUDIT {
uuid account_merge_audit_id PK
uuid survivor_user_reference_id FK
uuid duplicate_user_reference_id FK
text match_evidence_code
text operation_outcome_code
uuid actor_identity_id
timestamptz occurred_at
}

USER_OPERATION_LOCK_STATE {
uuid user_operation_lock_state_id PK
uuid keycloak_user_reference_id FK
text operation_type_code
text lock_owner_token
timestamptz acquired_at
timestamptz lease_expires_at
}
```

## Logical uniqueness constraints

UUID primary identifiers are globally unique. Human/provider identifiers are scoped to the owning tenant or federation source and MUST NOT be interpreted as global keys.

| Entity | Required logical uniqueness |
|---|---|
| `IDP_CONFIG_ENTRY` | `(tenant_deployment_id, config_key)` |
| `FEDERATION_SOURCE` | `(tenant_deployment_id, federation_alias)` |
| `DIRECTORY_FEDERATION_SOURCE` | `(tenant_deployment_id, directory_alias)` |
| `RELYING_PARTY_SOURCE` | `(tenant_deployment_id, client_id)` |
| `KEYCLOAK_USER_REFERENCE` | `(tenant_deployment_id, keycloak_user_uuid)` |
| `EXTERNAL_IDENTITY_LINK` | `(federation_source_id, external_subject_hash)` |

`federation_source_id` defines the identity-provider scope for the external-subject uniqueness rule. Within one federation source, one normalized/hashed external subject may link to at most one Keycloak user reference. This prevents one issuer/provider subject from being attached to multiple users while still allowing unrelated providers to use the same subject string.

Physical migrations must enforce these constraints in the owning Keyverse store. Documentation labels such as `client_id`, `federation_alias`, or Keycloak UUID never authorize cross-tenant lookup by themselves.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs/ERD.md relevant sections =="
sed -n '120,175p' docs/ERD.md | cat -n -v

echo
echo "== Search related table definitions/constraints =="
rg -n "EXTERNAL_IDENTITY_LINK|ACCOUNT_MERGE_AUDIT|tenant_deployment_id|client_id|federation_alias|keycloak_user_reference_id|federation_source_id" docs -S || true

echo
echo "== Search tenant references for related entities =="
rg -n "tenant_deployment_id|tenant_id|MULTI_TENANT|tenant" . -S --glob '!node_modules' --glob '!dist' --glob '!build' | head -200

Repository: ContextualWisdomLab/keyverse

Length of output: 14893


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs/ERD.md table definitions =="
sed -n '1,120p' docs/ERD.md | cat -n -v

echo
echo "== Full references to ACCOUNT_MERGE_AUDIT / EXTERNAL_IDENTITY_LINK =="
rg -n "ACCOUNT_MERGE_AUDIT|EXTERNAL_IDENTITY_LINK|survivor_user_reference_id|duplicate_user_reference_id|keycloak_user_reference_id|federation_source_id|tenant_deployment_id" . \
  -S --glob '!node_modules' --glob '!dist' --glob '!build' \
  | sed -n '1,240p'

echo
echo "== Read-only check: ERD FK constraints and tenant columns for target tables =="
python3 - <<'PY'
from pathlib import Path
p=Path('docs/ERD.md')
text=p.read_text()
targets = ['FEDERATION_SOURCE','KEYCLOAK_USER_REFERENCE','EXTERNAL_IDENTITY_LINK','ACCOUNT_MERGE_AUDIT']
in_table=False
current=None
for line in text.splitlines():
    if re := any(f'  {{{t}' in line for t in targets):
        m = next(t for t in targets if f'  {{{t}' in line)
        current=m
        in_table=True
        print(f'\n[{current}]')
        continue
    if in_table:
        if line.startswith('    }'):
            in_table=False
            current=None
            continue
        print(line)
PY

Repository: ContextualWisdomLab/keyverse

Length of output: 7406


테넌트 간 관계를 데이터베이스 제약으로 차단하세요.

EXTERNAL_IDENTITY_LINKACCOUNT_MERGE_AUDITtenant_deployment_id 없이 FK만으로 부모 레코드를 참조합니다. 두 부모가 같은 TENANT_DEPLOYMENT에 속하는지 검증하는 복합 FK 또는 동등한 제약을 추가해야 합니다. 이 제약을 추가하지 않으면 테넌트 A의 federation source가 테넌트 B의 Keycloak user reference/merge audit과 연결될 수 있어 Line 154의 테넌트 격리를 구현 단계에서 보장할 수 없습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ERD.md` at line 154, Update the physical migration constraints for
EXTERNAL_IDENTITY_LINK and ACCOUNT_MERGE_AUDIT so each child record references
parent records within the same TENANT_DEPLOYMENT, using composite foreign keys
or an equivalent database constraint that includes tenant_deployment_id. Ensure
cross-tenant federation-source, Keycloak-user, and merge-audit associations are
rejected regardless of documentation labels.


## Identity and authorization rules

- Keycloak UUIDs, federation aliases, RP client IDs, email values, and external subjects are data identifiers, not authorization by themselves.
- Exact external identity key is `(identity_provider, subject)`; verified email may support matching under policy but unverified email never authorizes linking.
- `tenant_deployment_id` is explicit in Keyverse-owned records; deployment/customer separation must not be inferred from realm/resource names.
- Secrets are referenced through protected values/handles where possible; secret-free desired-state tables must never gain client/bind credentials accidentally.

## Desired-state and receipt invariant

```mermaid
flowchart LR
PRIVATE[Private rendered input]
VALID[Preflight validation]
INTENT[Versioned desired-state source]
REMOTE[Keycloak live state]
RECEIPT[Version-bound apply receipt]

PRIVATE --> VALID
VALID --> INTENT
INTENT --> REMOTE
REMOTE --> RECEIPT
```

Every apply receipt records the exact `desired_state_hash` that was acted on as well as the canonical `observed_state_hash`, outcome, unique `apply_attempt_id`, and observation time. A receipt is current for a source only when its `desired_state_hash` equals that source's current desired-state hash. The latest current receipt is the greatest `observed_at` among receipts for that exact desired-state hash; a receipt for an older hash is historical evidence and cannot establish convergence for a newer desired state.

Retry handling is idempotency-aware. Reusing the same `apply_attempt_id` must return/reuse the same receipt rather than create a second logical attempt. A retry under a new attempt ID may create another receipt, but it remains a distinct attempt and must still bind to the exact desired-state hash. Delete flows that require remote-first semantics cannot remove local desired state before remote deletion succeeds.

## Keycloak ownership

Users, sessions, roles, groups, credentials, WebAuthn material, IdP runtime representation, LDAP storage components, and RP clients ultimately live in Keycloak's schema/API. Keyverse stores controlled references/intent/receipts but does not duplicate or directly edit unsupported Keycloak internal tables.

## Migration acceptance

Changes to Keyverse-owned persistence require migrations/rollback, transaction/concurrency tests, indexes/constraints, tenant isolation, secret/logging tests, backup/restore impact, and ERD/operability/ADR synchronization. Keycloak upgrades require supported schema migration through Keycloak, not custom manipulation of its private database tables.
97 changes: 97 additions & 0 deletions docs/OPERABILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Keyverse Operability, Recovery, and Release Guide

**Status:** Accepted cross-cutting operating baseline
**Last reviewed:** 2026-08-09

Feature-specific procedures under `docs/operations/`, federation/RP onboarding, and deployment READMEs remain authoritative for their slices. This guide defines the shared operating model and evidence needed before declaring the identity platform healthy or release-ready.

## Health model

Distinguish these conditions:

1. **process liveness:** Keycloak/admin process responds;
2. **component readiness:** database/config/bootstrap and core dependencies are usable;
3. **desired-state convergence:** configured federation/directory/RP state matches Keycloak;
4. **protocol acceptance:** controlled login/logout/token/SCIM/bind/search behavior succeeds;
5. **downstream authorization acceptance:** RP accepts expected issuer/audience/claims and applies its own authorization policy.

A lower-level green state never implies a higher-level state.

## Key SLIs

- Keycloak/admin readiness and latency;
- login/passkey success/error rates;
- SCIM mutation success/conflict/retry/lock contention;
- account merge/link outcomes and rollback/tombstone anomalies;
- desired-state drift and reconciliation age;
- federation/LDAP/RP apply/re-observation failures;
- duplicate remote resource detections;
- user-operation lock wait/expiry/recovery;
- token issuer/audience/claim acceptance failures;
- database/storage availability and transaction errors;
- secret/config bootstrap failures;
- hourly governance run outcomes without false-green classification.

Do not put raw tokens, secrets, passwords/bind credentials, protected private payloads, or unnecessary PII into metrics/logs.

## Federation onboarding runbook

1. render private tenant configuration from approved KV/secret source;
2. run authenticated side-effect-free Keyverse preflight;
3. review exact policy result;
4. persist/apply desired state through the owning reconciliation path;
5. verify exact post-mutation Keycloak state/receipt;
6. for LDAP/AD perform controlled bind/search/login acceptance after explicit apply;
7. for SAML/OIDC perform controlled login/issuer/subject/email/trust checks;
8. monitor convergence/errors;
9. retain rollback data until acceptance criteria expire.

## RP onboarding runbook

1. submit secret-free client representation;
2. preflight redirects/origins/logout/PKCE/scopes/type;
3. reconcile exact Keycloak client and receipt;
4. provision confidential secret through the separate secret-management path if needed;
5. configure RP securely;
6. run authorization-code/PKCE login/logout/token audience/claim acceptance;
7. validate downstream authorization separately from authentication.

PR #72's mapper profile requires the same acceptance after merge: operators must test the **Naruon** product login/token/authorization journey using the `naruon-web` RP client ID and verify the expected audience and bounded claims. Mapper unit tests alone do not prove Naruon product authorization readiness.

## Account merge recovery

Merge and SCIM full replacement (`PUT`) must hold the shared operation lock. Protected-main `PATCH active=false` is not currently inside that shared-lock guarantee and must not be treated as transactionally serialized with merge. On failure, classify whether state changed in Keycloak, Keyverse audit, linked identities, or tombstone status. Re-observe before retry. Never infer a retry is safe solely from the previous HTTP response. Preserve survivor and duplicate lineage in audit.

## Desired-state recovery

On controller/API crash after intent but before receipt:

- read persisted desired state;
- query exact remote Keycloak state;
- classify converged, absent, duplicate, or drifted;
- reconcile idempotently;
- write receipt only after exact re-observation and bind it to the desired-state version/hash that was applied.

On delete, keep local intent until remote-first deletion has succeeded where required.

## Database/backup

Back up Keycloak PostgreSQL and Keyverse-owned configuration/audit/intent/receipt state according to deployment RPO/RTO. Restore through supported Keycloak/database procedures, then run reconciliation and controlled authentication/provisioning acceptance. Do not edit unsupported Keycloak internal tables as a normal recovery technique.

## Upgrade/rollback

- review Keycloak release/migration notes and Keyverse CHANGELOG/ADRs;
- rehearse database migration and Helm/Compose upgrade;
- validate realm/config/template compatibility;
- run merge/SCIM/federation/RP suites;
- canary controlled login/provisioning;
- roll back application/config where safe and use supported DB backup/restore for incompatible schema migrations;
- re-run convergence and protocol acceptance after rollback.

## Automation incident RCA

PR #74 demonstrates that a workflow can appear successful while doing no useful work if a GitHub API gate fails open. Scheduled governance must classify transport failure separately from a valid empty/unhealthy result, fit its time budget, keep provider secrets in the broker phase only, and require exact `success` for protected evidence. After PR #74 merges, operational closure requires a real protected-main scheduled/manual run.

## Release gate

Release only after protected-head CI/security/review, 100% coverage/docstrings, realm/package/deployment validation, migrations/rollback/backup, passkey/federation/SCIM/RP controlled acceptance, secret scan, SBOM/provenance/image digest, runbooks/support, and CHANGELOG/version artifacts are coherent. A merged PR is not a release by itself.
Loading
Loading