Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
1c834e6
docs(authz): record the non-fork RP authorization boundary
Aug 11, 2026
552fcae
docs(authz): record downstream dependency gate
Aug 11, 2026
c8e3656
docs(authz): make RP contract reproducible
Aug 11, 2026
4da37dc
docs(authz): track latest SDP review head
Aug 11, 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
7 changes: 7 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ provisioning remains an independent secret-management responsibility.
Native loopback/private-use redirects and deployment-specific claim expansion
remain separate reviewed profiles.

Each downstream RP is a separate trust boundary. The RP must validate the
Keyverse issuer, signature/algorithm, expiry, subject, and audience, map the
verified tenant (`org`/deployment mapping), apply resource and purpose ABAC,
and then apply bounded role/scope/group RBAC. A registered client or accepted
mapper receipt never grants authorization by itself; see ADR-0008 for the
non-fork application matrix and remediation gates.

## Account and provisioning invariants

1. Matching precedence is exact `(identity_provider, subject)`, then verified
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Keep a Changelog, and releases use semantic versioning.

### Added

- ADR-0008 and the non-fork RP authorization matrix, requiring explicit
Keyverse token validation, tenant/resource ABAC, bounded RBAC, and
cross-tenant acceptance evidence per application.

- Durable, secret-free OIDC relying-party desired-state CRUD and reconciliation
with exact `clientId` matching, duplicate fail-closed behavior, post-mutation
re-observation, canonical apply receipts, realm-rebuild recovery, per-client
Expand Down
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.
208 changes: 208 additions & 0 deletions docs/ERD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# 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 tenant_deployment_id FK
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 tenant_deployment_id FK
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.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Tenant-qualified composite foreign keys are mandatory for cross-entity identity
references. At minimum, the physical schema MUST enforce:

- `EXTERNAL_IDENTITY_LINK (tenant_deployment_id, keycloak_user_reference_id)` →
`KEYCLOAK_USER_REFERENCE (tenant_deployment_id, keycloak_user_reference_id)`;
- `EXTERNAL_IDENTITY_LINK (tenant_deployment_id, federation_source_id)` →
`FEDERATION_SOURCE (tenant_deployment_id, federation_source_id)`;
- `ACCOUNT_MERGE_AUDIT (tenant_deployment_id, survivor_user_reference_id)` →
`KEYCLOAK_USER_REFERENCE (tenant_deployment_id, keycloak_user_reference_id)`;
- `ACCOUNT_MERGE_AUDIT (tenant_deployment_id, duplicate_user_reference_id)` →
`KEYCLOAK_USER_REFERENCE (tenant_deployment_id, keycloak_user_reference_id)`.

The referenced tenant-qualified pairs MUST be unique keys. A child row carrying
only a globally unique UUID is insufficient evidence of tenant isolation; the
database constraint must reject a mismatched tenant even when application code
or documentation labels are bypassed.

## 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.
102 changes: 102 additions & 0 deletions docs/OPERABILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 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.

For every non-fork RP, record the exact Keyverse issuer, audience, JWKS/key
rotation source, claim-to-tenant mapping, ABAC/RBAC result, cross-tenant denial
result, and production-mode fail-closed result. A successful Keyverse client
receipt or mapper check is not sufficient acceptance evidence.

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