Skip to content
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,18 @@ Keep a Changelog, and releases use semantic versioning.

### Changed

- Buyer README and accepted ADRs 0001–0007 now describe Keyverse as a
standalone identity leaf/hub, point operators at published OIDC/OAuth
2.0, SAML, LDAP, and SCIM contracts, and cite independently opened
official records in `docs/REFERENCES.md`. OAuth 2.1 is labeled an IETF
Internet-Draft, not a final RFC.
- Updated the design-only MCP authorization contract to MCP Authorization
2026-07-28, RFC 9207 callback-issuer validation, and RFC 9068 JWT
access-token header, claim, signature, and algorithm rejection evidence;
runtime acceptance remains unimplemented.
- Added the product/technical gap baseline and its APA 7th doctoring record,
including the current exact-head PR/Issue inventory and explicit
`gap-not-claimed` runtime and release boundaries.
- Relying-party deployment controllers now send validated, secret-free metadata
to Keyverse desired-state PUT instead of applying client representations
directly to Keycloak; confidential credential placement remains a separate
Expand Down
3 changes: 2 additions & 1 deletion DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Keyverse already has strong feature-specific specifications, doctoring, federati
| 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) |
| Product and technical gap baseline | [`docs/product-technical-gap-baseline.md`](docs/product-technical-gap-baseline.md) and [`docs/doctoring/product-technical-gap-baseline.md`](docs/doctoring/product-technical-gap-baseline.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) |
Expand All @@ -34,4 +35,4 @@ Keyverse already has strong feature-specific specifications, doctoring, federati
- **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.
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.
243 changes: 131 additions & 112 deletions README.md
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,59 +1,72 @@
# cwl-idp — ecosystem central IdP

The **ContextualWisdom ecosystem's central Identity Provider**, a standalone
component built on [**Keycloak**](https://www.keycloak.org) (Apache-2.0). It:

- issues **OIDC / OAuth 2.1** to ecosystem relying parties (`naruon`,
`pg-erd-cloud`, `semantic-data-portal`, `clearfolio`, `contextual-orchestrator`,
and `newsdom-api` through the WAF edge);
- is **passwordless-first**: FIDO2 / passkeys are the default and the **password
authenticator is removed** from the login flow for ecosystem-local accounts;
- runs a **SCIM v2 server shim** for inbound provisioning into Keycloak;
# Keyverse (cwl-idp)

Keyverse is the ContextualWisdomLab **identity leaf and hub**. It is the system of
record for **who a person is in this ecosystem**: local passwordless accounts,
inbound federation, inbound SCIM provisioning, and outbound OpenID Connect
tokens that relying parties consume.

It is **not** the employment or org-tree system of record. Orgmetra owns
employment and organizational-tree truth. Keyverse does not copy Orgmetra
tables. Composition hubs such as **naruon** and **gyeot** may call this leaf;
they are not required to boot it.

Keyverse must run **from this repository alone** (Compose or Helm in this repo)
and remain **callable** by relying parties over published OIDC/OAuth, SAML
broker, LDAP/AD user-storage, and SCIM contracts.

## What this IdP does

Built on [Keycloak](https://www.keycloak.org) (Apache-2.0) plus a Keyverse
account-unification admin service, the product:

- issues **OpenID Connect** on **OAuth 2.0** to ecosystem relying parties
(`naruon`, `pg-erd-cloud`, `semantic-data-portal`, `clearfolio`,
`contextual-orchestrator`, and `newsdom-api` through the WAF edge);
- is **passwordless-first**: FIDO2 / passkeys are the default, and the
**password authenticator is removed** from the bound browser flow for
ecosystem-local accounts;
- runs a **SCIM 2.0 server shim** for inbound provisioning into Keycloak;
- **federates external IdPs in** — employer ADFS via SAML, corporate LDAP/AD,
and optional personal OIDC — while keeping unverified email ineligible for
account linking; and
- adds an **account-unification** admin service to link one human's many external
identities and to **merge** two pre-existing accounts into one.
and optional personal OIDC — as **deployment data**, never as portable realm
code; and
- links one human's many external identities and **merges** two pre-existing
accounts into one survivor, never on an unverified email.

> Employer ADFS and corporate directories are **external compatibility
> targets**, not peer hubs. Customer-specific federation stays in the
> deployment controller and KV store.

> Employer ADFS and corporate directories are **external, proprietary**
> compatibility targets—not the hub. cwl-idp is the hub, and customer-specific
> federation remains deployment data rather than portable realm code.
OAuth 2.0 ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749)) is the official
authorization-framework record. [OAuth 2.1](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)
is an IETF Internet-Draft (`draft-ietf-oauth-v2-1-15`, work in progress) and
is not cited here as a final RFC.

RP client registrations and secrets live in the **IdP DB / KV**, never in an RP's
environment.
RP client registrations and confidential values live in the **IdP DB / KV**,
never in an RP's environment. Authorized identity data stays usable under
purpose-bound access control, encryption, and audit.

## Architecture

```text
external IdPs ──► cwl-idp (Keycloak) ──► OIDC to ecosystem RPs
ADFS (SAML) passwordless OIDC/OAuth
external IdPs ──► Keyverse (Keycloak + admin service) ──► OIDC to RPs
ADFS (SAML) passwordless OIDC / OAuth 2.0
LDAP/AD FIDO2 passkeys
OIDC (opt) SCIM v2 shim (inbound)
OIDC (opt) SCIM 2.0 shim (inbound)
HR/IGA (SCIM) account-unification admin service
```

Architecture and trust boundaries: [`ARCHITECTURE.md`](ARCHITECTURE.md). Full
network diagram: [`docs/topology.md`](docs/topology.md).

## Repository layout
composition hubs (naruon, gyeot) MAY call this leaf
Orgmetra owns employment / org-tree truth (not copied here)
```

| Path | What |
| --- | --- |
| `docker-compose.yml` | Standalone bring-up: Keycloak + Postgres + admin service (pinned by digest) |
| `deploy/keycloak/` | Portable Keycloak realm config-as-code, passwordless flows, shared scopes, concrete Naruon RP, and service-account bootstrap |
| `deploy/templates/` | Private deployment templates split between Keyverse preflight/desired state and explicit Keycloak Admin REST apply contracts |
| `deploy/bootstrap/` | Bootstrap pointer to the KV/DB config store |
| `deploy/scripts/healthz.sh` | Cross-component readiness probe |
| `scripts/validate_realm.py` | Realm config-as-code validator (CI gate) |
| `services/account_unification/` | FastAPI admin service (link + merge + SCIM + federation validation/desired state) with unit tests |
| `helm/cwl-idp/` | Helm chart (templated Keycloak + Postgres + admin service) |
| `docs/operations/` | Scheduled maintenance and product-development operating procedures |
| `docs/doctoring/` | Standards interpretation and APA 7th engineering traceability |
| `docs/` | Topology, passwordless policy, federation, merge flow, RP onboarding, and papers |
Trust boundaries: [`ARCHITECTURE.md`](ARCHITECTURE.md). Network diagram:
[`docs/topology.md`](docs/topology.md). Architecture decisions:
[`docs/adr/`](docs/adr/README.md). Standards bibliography:
[`docs/REFERENCES.md`](docs/REFERENCES.md).

## Quick start (standalone)
## Run this repository alone

Requires Docker or Podman with the compose plugin.
No sibling repository checkout is required. Docker or Podman with the compose
plugin is enough:

```bash
cp .env.example .env # populate values from your KV (bootstrap transport)
Expand All @@ -71,93 +84,99 @@ The stack imports the **passwordless-first** realm at first start
WebAuthn passwordless authenticator and **no password authenticator**, plus
`registrationAllowed:false` / `resetPasswordAllowed:false`.

### Register external federation
Production-shaped clusters use [`helm/cwl-idp/`](helm/cwl-idp/).

The portable realm contains no employer ADFS, LDAP/AD source, or other
customer-specific federation. Render deployment values from KV and preflight
every private payload before apply:
### Optional parent include

- SAML and external OIDC:
`POST /federation/identity-providers:validate`, followed by the Keyverse
desired-state `PUT` and reconciliation flow.
- LDAP and Active Directory:
`POST /federation/user-directories:validate`, followed by deployment-owned
private Keycloak component apply. The first profile is LDAPS-only,
read-only, Kerberos-disabled, and `trustEmail=false`.
A parent Compose or Helm chart **may** include this repo's
`docker-compose.yml` or depend on `helm/cwl-idp`. That is an optional
embed of **this** repository. Keyverse does not require naruon, gyeot,
Orgmetra, or any other sibling checkout in order to start.

LDAP preflight performs no DNS lookup, socket connection, bind, search, KV/DB
write, or Keycloak call. Its response redacts `bindDn` and `bindCredential` and
must never be used as the apply payload; apply the original private file only.
## How a relying party calls Keyverse

See [`docs/federation-onboarding.md`](docs/federation-onboarding.md),
[`deploy/keycloak/README.md`](deploy/keycloak/README.md), and
[`deploy/templates/README.md`](deploy/templates/README.md).
Each RP is a separate trust boundary. A README listing, repository
relationship, or client ID is not authorization. The RP validates issuer,
signature, audience, subject, and expiry, then applies its own
access-control policy ([ADR-0008](docs/adr/0008-keyverse-rp-authorization-boundary.md)).

### Onboard a relying party
Published operator contracts that already ship:

See [`docs/rp-onboarding.md`](docs/rp-onboarding.md).
| Contract | Purpose |
| --- | --- |
| Keycloak OIDC endpoints on the WAF edge | Authorization, token, JWKS, and logout for registered RPs |
| `POST /clients/relying-parties:validate` | Side-effect-free RP client preflight |
| `PUT /clients/relying-parties/{client_id}` | Secret-free RP desired state and reconcile |
| `POST /federation/identity-providers:validate` | Side-effect-free SAML/OIDC IdP preflight |
| `PUT /federation/identity-providers/{alias}` | Persist and converge an external IdP |
| `POST /federation/user-directories:validate` | Side-effect-free LDAP/AD preflight (no DNS, socket, bind, search, store, or Keycloak call) |
| `PUT /federation/user-directories/{name}` | Persist and converge a directory component |
| `/scim/v2/Users` | Inbound SCIM 2.0 user lifecycle |
| `POST /registration/accounts` | Password-free account create plus enrollment email |
| `GET /users/{user_id}`, `POST /merges` | Inspect and merge accounts |

Confidential client secrets are placed by the deployment controller, not
returned in ordinary Keyverse responses. See
[`docs/rp-onboarding.md`](docs/rp-onboarding.md).

## Account unification & merge
### Register external federation

```bash
cd services/account_unification
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytest -q
```
The portable realm contains no employer ADFS, LDAP/AD source, or other
customer-specific federation. Render deployment values from KV and preflight
every private payload before apply.

Design: [`docs/merge-unification-flow.md`](docs/merge-unification-flow.md).
Matching precedence is **exact (idp, subject) → verified email → explicit link**,
and the engine **never merges on an unverified email**.
LDAP preflight redacts `bindDn` and `bindCredential` and must never be used
as the apply payload; apply the original private file only. The first
directory profile is LDAPS-only, read-only, Kerberos-disabled, and
`trustEmail=false`.

## Standalone AND submodule-embeddable
See [`docs/federation-onboarding.md`](docs/federation-onboarding.md) and
[`docs/ldap-directory-onboarding.md`](docs/ldap-directory-onboarding.md).

- **Standalone:** the compose file or the Helm chart.
- **Submodule:** add this repo as a git submodule and `include:` its
`docker-compose.yml`, or depend on `helm/cwl-idp`. Every component exposes a
`/healthz`-style readiness probe so the parent can gate on it.
## Account unification

Comment thread
seonghobae marked this conversation as resolved.
## Configuration & secrets
Matching precedence is **exact `(identity_provider, subject)` → verified
email → explicit operator link**. The engine **never merges on an unverified
email**. Merged duplicates remain disabled tombstones with survivor lineage.
Design: [`docs/merge-unification-flow.md`](docs/merge-unification-flow.md).

## Configuration and secrets

Config and secrets are read from the **KV / DB store**, not from runtime
`os.getenv`. Environment variables are used **only as bootstrap transport** to
reach that store (`CWL_IDP_BOOTSTRAP` → `deploy/bootstrap/bootstrap.yaml`).
Database objects use two-word snake_case names (`idp_config_entries`,
`account_merge_audit`).
`os.getenv`. Environment variables are **bootstrap transport** only
(`CWL_IDP_BOOTSTRAP` → `deploy/bootstrap/bootstrap.yaml`). Database objects
use two-word-or-longer snake_case names (`idp_config_entries`,
`account_merge_audit`, `user_operation_lock_state`).

## Engine & licensing
## Engine and licensing

- Engine: **Keycloak** (Apache-2.0). This repo: **Apache-2.0** (`LICENSE`).
- **Permissive OSS only** — no GPL/AGPL dependencies. cwl-idp deliberately does
**not** use ZITADEL (AGPL-3.0) nor the commercial scim-for-keycloak plugin;
the SCIM shim in this repo is our own Apache-2.0 code.

## References

Standards and papers live under `docs/papers/` and `docs/doctoring/`, including
NIST SP 800-63C federation, RFC 7644 SCIM, OIDC Core, SAML V2.0, and the LDAP
RFC 4511–4515 family.

---
- **Permissive OSS only** — no GPL/AGPL dependencies. The SCIM shim is
Apache-2.0 code in this repository.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Where decisions and standards live

## Hourly OpenCode product development

At minute 41 UTC, and only when no pull request exists and the exact `main` SHA
is healthy, Keyverse may run one bounded OpenCode development cycle through a
loopback NVIDIA NIM credential broker. The model works from a disposable
`git archive` without `.git`, GitHub credentials, Actions OIDC, publication
authority, or the upstream NIM credential.
| Path | What |
| --- | --- |
| [`docs/adr/`](docs/adr/README.md) | Accepted architecture decisions (0001–0008 on this branch) |
| [`docs/REFERENCES.md`](docs/REFERENCES.md) | APA 7th bibliography for ADR 0001–0007 |
| [`docs/doctoring/`](docs/doctoring/) | Feature-specific standards interpretation |
| [`docs/product-technical-gap-baseline.md`](docs/product-technical-gap-baseline.md) | Current buyer-visible product and technical gap register |
| [`docs/papers/`](docs/papers/README.md) | Offline copies of selected primary sources |
| [`docs/operations/`](docs/operations/) | Operator runbooks, including hourly product development |
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | Runtime topology and trust boundaries |
| [`docs/rp-onboarding.md`](docs/rp-onboarding.md) | RP onboarding |
| [`docs/passwordless-policy.md`](docs/passwordless-policy.md) | Passwordless realm invariants |

A fresh job independently validates the sealed patch and re-runs the complete
100% production docstring, statement, and branch coverage gates plus package,
realm, Compose, and provider-template checks. Only then may a dedicated
`OPENCODE_PRODUCT_DEVELOPMENT_TOKEN` create one draft PR. Existing review-agent
workflows and credentials are unchanged; the development workflow cannot
approve, merge, tag, or release.
## Repository layout

Operations are documented in
[`docs/operations/hourly-product-development.md`](docs/operations/hourly-product-development.md).
Standards traceability and APA 7th references are recorded in
[`docs/doctoring/hourly-opencode-product-development.md`](docs/doctoring/hourly-opencode-product-development.md).
| Path | What |
| --- | --- |
| `docker-compose.yml` | Standalone bring-up: Keycloak + Postgres + admin service (pinned by digest) |
| `deploy/keycloak/` | Portable Keycloak realm config-as-code and service-account bootstrap |
| `deploy/templates/` | Private deployment templates for preflight and desired state |
| `deploy/bootstrap/` | Bootstrap pointer to the KV/DB config store |
| `deploy/scripts/healthz.sh` | Cross-component readiness probe |
| `scripts/validate_realm.py` | Realm config-as-code validator |
| `services/account_unification/` | FastAPI admin service (link, merge, SCIM, federation, RP desired state) |
| `helm/cwl-idp/` | Helm chart for the same three components |
Loading