Identity field validation must match every system the values are projected into #66
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-docs#66
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "%!s()"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Three systems each enforce their own rules on a username and an email address — the portal, the
identity provider and the git host — and the portal's rules were never reconciled with either of
the other two. A value the portal accepts can be refused downstream, and the worst case is silent.
An audit at the pinned versions (git host
16.0.1, identity provider1.10.4, reconcilerv1.2.1) found the portal is more permissive than the intersection on several dimensions:alice.,bob-,a--b,api,explore,ghost,robots.txt,alice.keysall pass sign-up and are accepted by the identity provider, then theaccount is unusable: the first sign-in to the git host fails with a 500. No error is shown to the
user, nothing is logged, no metric moves and no alert fires.
7alice), anything UUID-shaped, or a non-ASCII address such asanvändare@example.seis refused by the identity provider behind a generic "check your details".On an email change the single-use token is consumed before the write, so retrying fails
identically, forever.
consent record is keyed on the spelling that was submitted — so the two no longer match, and
nothing errors.
the alert that fires names the wrong subsystem and sends the operator after a token scope. It also
retries forever, so it sticks.
Production is currently clean — all identity metrics at zero, no failures in fourteen days of logs.
That is luck rather than a control.
Decision taken
The username is normalised to lower case visibly at the form, so what the user sees is what is
provisioned everywhere. The identity provider does this silently already; making it visible removes
the divergence rather than hiding it.
Scope
rules, enforcing the intersection of all three systems, imported by every surface that
validates them
one generic failure
(tracked on the reconciler board)
creation at first sign-in
upgrade obligation
Sequencing (operator decision)
Observability lands first. Nothing here has bitten anyone yet — production shows zero identity
failures over fourteen days — so the first priority is that a real occurrence is visible and
correctly attributed rather than reported as a CI-enforcement fault. The reconciler's separate
identity gauge and gitborg/gitborg-infra#326 therefore precede the validation rules in
gitborg/gitborg-web#151 and gitborg/gitborg-web#152.
Done when
A value accepted by the portal is accepted by both downstream systems, a rejected one says which
rule it broke, and an upgrade that changes a downstream rule fails a test rather than a user.
Status — 2026-08-02 (complete)
Observability landed first, as decided, and the validation rules followed. All four are merged and
applied to production; the reconciler runs v1.3.0 and publishes
gitborg_reconciler_identity_projection_degradedandgitborg_reconciler_identity_failures, both0, with the Actions gauge finally meaning only what its name says.
IdentityProjectionDegradedandthe log-based
ForgejoAccountCreationRefusedare live and evaluating.The design note landed as
design/identity-field-validation.md, linked from ADR 0038 and added toADR 0028's upgrade decisions as item 8. Writing it surfaced one thing the audit had not: the display
name is validated by a separate module and is far less constrained than the other two fields, so
there is no intersection to compute for it — only a non-empty, no-control-characters, 100-character
rule, each for a stated reason.
The build scope is delivered. What the note makes explicit is that the remaining risk is not
buildable: nothing detects upstream drift on its own, because the tests assert our transcription of
the upstream rules rather than upstream itself. Re-reading the source at every version bump is the
control, and it now lives in the upgrade cadence decision record rather than in anyone's memory.
All five scope items are delivered, so this epic closes per the closing rule in the issue-tracking guide: the build scope is done, and what remains is a standing obligation rather than work — re-reading the upstream validation rules at every Forgejo or Kanidm bump, now recorded as decision 8 in ADR 0028 and detailed in
design/identity-field-validation.md.