Make the Keycloak transform preserve or name every policy field
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s

keycape-to-keycloak called Transform, which passes no clients, so it wrote a
realm with an empty clients array and nothing said the service-identity contract
had not been migrated. Where clients were supplied, mapClient hardcoded
standardFlowEnabled — silently giving every client_credentials registration the
browser flow — and dropped audience, service subject, tenant, roles, lifetime,
MFA policy, secret reference and handoff URLs. Realm roles and client scopes were
emitted as empty containers.

The defect was not the missing mapping but that a dropped field and an
inapplicable one looked identical in the output.

Add -clients, reading registrations through a new config.Registrations() that
converts without resolving secrets, so migration tooling cannot load material it
has no business holding. Derive flows from the declared grants. Carry the profile
claims as protocol mappers, since Keycloak has no native concept for them, and
lifetime, handoff URLs and the secret reference as attributes — the reference,
never a value. Derive realm roles and client scopes from what is present.

Report what cannot be carried, in UnpreservedReport, kept deliberately separate
from ValidationReport: consistency with the snapshot and completeness of the
migration are different questions and one list cannot answer both. It names the
unmigrated secret, the unenforceable MFA policy, passwords and factor enrolment,
and subject continuity. An incomplete transform emits partial telemetry.

Closes the semantic-preservation half of G03; proof against a live provider is
G04 and stays open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WAsfsfQmDu4vcBhiMcmQp

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 867844@bnt-lap001
Assistant-Session: 3d45905e-0016-4b49-b828-231406881f7b
This commit is contained in:
tegwick 2026-09-07 13:48:48 +02:00
parent 8707d375a2
commit e9fc8544ab
12 changed files with 664 additions and 56 deletions

View file

@ -171,6 +171,30 @@ transforms preserve or explicitly reject every relevant policy/identity field,
and migration proof demonstrates subject continuity, claims, MFA and client
behavior. Until then, call these artifact generators rather than full migration.
**Status 2026-09-07 (KEY-WP-0020): semantic preservation closed; proof remains
G04.** The CLI takes `-clients` and reaches `TransformWithClients`, so the realm
carries the registration contract instead of an empty array. Flows are derived
from the declared grants rather than hardcoded — the previous mapping enabled the
browser flow on every service-only client, widening it during migration. The
audience, tenant, service subject and roles ride as protocol mappers, per-client
lifetime and handoff URLs as attributes, and the secret *reference* as an
attribute, never a value; a test asserts no resolved secret can appear in the
JSON. Realm roles and client scopes are derived from the identities and
registrations present, replacing empty containers that made a realm dropping
every role look like one that had none.
What cannot be carried is now named rather than dropped, which was the real
defect: `UnpreservedReport` is deliberately separate from `ValidationReport`, so
"the realm matches the snapshot" and "the migration is complete" stay distinct
questions. It names the unmigrated secret, the unenforceable MFA policy,
passwords and factor enrolment, and subject continuity — Keycloak mints its own
`sub`, so relying parties keyed on it will not recognise migrated users. An
incomplete transform emits `partial` telemetry, matching KEY-WP-0018.
Not closed: this is preservation and honest reporting, not proof. Whether an
imported realm actually issues profile-conformant tokens is G04, and password and
MFA credential migration remains out of scope.
### G04 — The replacement test harness does not prove a live provider swap
**Priority: high. Kind: verification and tooling gap.**