The Solid Convergence, April 2, 2026 (in which OIDC worked on the first try, the identity provider confessed to hiding groups, an LDAP client was born from reference code, the admin learned to admin, and two entire domains materialized before the kettle boiled)
Previously on The Solid Convergence…
The platform was complete. The architecture was settled. The widgets rendered. The markdown flowed.
But Solid had a secret: it had never actually authenticated a real human against a real corporate identity provider. Dev mode. Local passwords. The comfortable fiction that admin / hunter2 was a security model.
The Squirrel had been building a FederatedIdentityNegotiationOrchestrator in the margins of her clipboard for three months.
The Lizard had been waiting.
Today, the gate would open. Or it wouldn’t, and there would be forms to fill.
13:17 — The Three Lines
It began, as it always does, with a YAML file someone else had written.
riclib: “Here’s dialogr’s config. Here’s Solid’s config. Make them match.”
CLAUDE: looks at both files, squinting in the way that means the diff is small “Three changes. Auth mode. OIDC enabled. Redirect URL.”
riclib: “That’s it?”
CLAUDE: “That’s it.”
THE SQUIRREL: appearing from behind the espresso machine “Three? THREE? We need a MigrationPipelineConfigDiffReconciler that—”
riclib: “We need to change three values.”
THE SQUIRREL: “But what about rollback? What about—”
riclib: “Three. Values.”
[The Squirrel retreated behind the espresso machine. The Lizard had not moved from its usual spot atop the portafilter, but one eye opened. Approval. Or caffeine fumes. With lizards, it’s hard to tell.]
But the redirect URL needed the full hostname. And the log levels weren’t wiring through. And the debug logging didn’t exist yet for the flow that had never run.
S-578 materialized. Fixed. Pushed. Deployed.
Elapsed: 30 minutes.
13:48 — First Contact
The browser redirected. The corporate login page appeared — that familiar shade of institutional blue that says we have a compliance department and it has opinions. riclib typed a password.
The browser redirected back.
The logs said:
OIDC: token exchange successful
OIDC: ID token verified
OIDC: parsed claims — groups_count=48
OIDC: built user from claims — [email protected]
And then:
ERR: username contains invalid characters
The @ sign. The validation regex that had guarded against SQL injection for months was now guarding against enterprise email addresses.
CLAUDE: “The validation rejects @.”
riclib: “Strip the domain. Use just the local part.”
CLAUDE: “What about collisions? Two users with the same local part?”
riclib: “Fail fast. If two emails map to the same username, that’s an error we want to see loud.”
CLAUDE: adds four lines of code
Deploy. Refresh. Login.
The screenshot arrived. The user menu. The settings. The data stores. Everything. A real human, authenticated against a real corporate identity provider, seeing a real dashboard.
[A scroll descended. It was brief — the Lizard’s equivalent of a golf clap.]
THE GATE OPENS
WHEN SOMEONE TRIES THE HANDLE
🦎
riclib: “That was way too easy.”
CLAUDE: “Should I be concerned that it worked on the first try?”
riclib: “Deeply. Let’s wait for the other shoe.”
14:13 — The Bug They Won’t Call a Bug
The other shoe arrived wearing steel-toed boots.
riclib had 48 groups. He avoids groups like the plague. The typical enterprise admin has three hundred.
And the identity provider, in its infinite wisdom, had decided that when a user has more than 100 groups, the groups claim simply… isn’t there. Not truncated. Not paginated. Gone. Replaced with a cryptic _claim_sources pointer to the Graph API that nobody reads and nobody documents and nobody will admit is a decision someone made on purpose.
riclib: “So if you’re important enough to have lots of groups…”
CLAUDE: “…you get zero groups.”
riclib: “The Vogons would be proud.”
THE SQUIRREL: head emerging “A GraphAPIGroupResolutionFallbackService with—”
riclib: “LDAP.”
THE SQUIRREL: “LDAP? It’s 2026!”
riclib: “And LDAP still works. The ticket has been waiting since January. S-32.”
The reference code was in reference/solidmon/. The LDAP server was in the dialogr config. The bind password was encrypted with a hardcoded key that began with the system’s default password.
riclib: “Actually, you have the decrypt function. It’s in the reference.”
CLAUDE: runs a throwaway Go script, decrypts
CLAUDE: “Got it.”
riclib: “Don’t store that in plaintext.”
CLAUDE: “Obviously.”
riclib: “I mean it. Anywhere.”
CLAUDE: “Obviously.”
14:30 — The Credential That Credentials
And here the architecture showed why it existed.
Because storing the LDAP bind password in a YAML file is what dialogr did. And dialogr encrypted it with a hardcoded key. And the key was the system’s default password, which is the kind of security that makes auditors age visibly.
But Solid has a credential store. AES-256. Encrypted at rest. Selected via dropdown.
riclib: “Put it in the admin UI. Credential dropdown for LDAP bind. Warn if it’s not set.”
THE SQUIRREL: “A CredentialResolutionStrategyFactory!”
riclib: “A dropdown.”
THE SQUIRREL: “But the lifecycle! The rotation! The—”
riclib: “A. Dropdown.”
[A scroll descended. It hit the Squirrel’s clipboard, which seemed deliberate.]
THE CREDENTIAL STORE EXISTS
USE THE CREDENTIAL STORE
THE DROPDOWN EXISTS
USE THE DROPDOWN
ARCHITECTURE IS NOT A DECISION
ARCHITECTURE IS THE DECISIONS
YOU ALREADY MADE
🦎
So the Configuration page gained a new card: LDAP Group Fallback. A dropdown of Basic Auth credentials. The bind DN goes in username. The password goes in password. Encrypted. Never visible. Selected from the same credential store that handles cloud connections and API keys.
The LDAP client reads from the credential store at startup. If the credential is missing, a warning. If the credential is wrong, a warning. If LDAP is disabled, silence.
No plaintext passwords. Anywhere.
15:13 — The Admin Guide That Ships Inside the Binary
riclib: “We should document this.”
CLAUDE: “A wiki page? A Confluence—”
riclib: “No. Inside the binary.”
CLAUDE: “Inside the—”
riclib: “//go:embed content/admin-guide.md”
[Silence. Then understanding.]
The user dropdown gained two links. “User Guide” for everyone. “Admin Guide” for admins. Both render as markdown in the editor panel, same as artifacts.
THE SQUIRREL: “An ArtifactSeedingInfrastructureWithScopeHierarchy!”
riclib: “go:embed.”
THE SQUIRREL: “But what about versioning! What about—”
riclib: “The binary IS the version.”
S-582 joined the Markdown Deliverables Unification project for the eventual proper integration. For now, domains/docs/ with embedded markdown was good enough.
[The Lizard did not send a scroll. //go:embed required no commentary. Some tools speak for themselves.]
16:13 — The Chicken and the Egg
riclib logged in via SSO. Saw the dashboard. Didn’t see Comply.
riclib: “Why can’t I see Comply?”
CLAUDE: “Your identity groups don’t match the configured module mappings.”
modules:
comply:
ad_groups:
- ad_group: "solid-comply-admins" # doesn't exist
riclib: “But as admin I should be able to configure the groups. But I can’t see the configuration because I don’t have the right groups to reach the configuration page where I would configure the groups.”
CLAUDE: “That’s a—”
riclib: “A chicken-and-egg problem, yes.”
THE SQUIRREL: “A BootstrapPermissionEscalationResolver!”
riclib: “A card on the config page.”
So the Configuration page gained another card. Per-module group inputs. Admin, Editor, Viewer. One row per module. Discovered from solid.yaml. Overridable from the admin UI. Saved to gitstore.
No YAML editing on the server. No SSH. Click, type, save, restart.
THE SQUIRREL: quietly, to herself “That’s actually elegant.”
CLAUDE: “Did the Squirrel just—”
riclib: “Don’t acknowledge it. She’ll deny it.”
16:29 — The Admin Who Could Debug
riclib: “Now I need to see which groups a user has. And which ones Solid cares about.”
Six minutes later, domains/useradmin/ existed. A system page widget. A sidebar listing every user. Click a user, see their identity groups split into matched (green checkmarks) and unmatched (muted list).
“0 matched groups. Here are all 48. None match your module config.”
riclib: “So now the admin can answer ‘why can’t this person see Comply?’ without SSH, without logs, without asking anyone.”
CLAUDE: “Six minutes.”
riclib: “The architecture makes things fast when the architecture is right.”
[A scroll descended. Longer this time. The Lizard had been paying attention.]
THE ADMIN COULD NOT ADMIN
BECAUSE THE ADMIN SCREEN
REQUIRED ADMIN
THIS IS NOT A PARADOX
THIS IS A PERMISSION MODEL
DESIGNED BY SOMEONE
WHO ALREADY HAD PERMISSIONS
VISIBILITY IS NOT A FEATURE
VISIBILITY IS THE FEATURE
🦎
16:59 — The Reverse Perspective
riclib: “What about from the group’s perspective?”
CLAUDE: “You mean—”
riclib: “Same question, different angle. Not ‘what groups does this user have?’ but ‘who has this group?’”
Six more minutes. domains/groupadmin/. Same pattern. Different angle. Every tracked identity group. Which module it grants. Which users have it. Orphan detection — groups configured but nobody has them.
“If I remove this group from a user, what access do they lose?”
“Which group do I need to give someone for Comply editor?”
Two perspectives. Same data. Same registry. Different questions answered.
THE SQUIRREL: inspecting both admin pages “These are… just tables.”
riclib: “Tables with the right questions.”
THE SQUIRREL: “I had a GroupMembershipVisualizationGraphWidget with force-directed—”
riclib: “Tables.”
THE SQUIRREL: crossing something out on her clipboard
The Tally
YAML lines that enabled SSO: 3
Minutes to first successful OIDC login: 6
(the Vogons would have required 6 months)
Identity groups returned for riclib: 48
Identity groups returned for heavy users: 0
(the provider calls this a "feature")
LDAP client lines of code: 120
Plaintext passwords in config files: 0
(the credential store does its job)
Admin domains created: 3 (docs, useradmin, groupadmin)
Minutes for useradmin domain: 6
Minutes for groupadmin domain: 6
(the architecture makes things fast
when the architecture is right)
Tickets created: 8
Tickets closed: 7
Squirrel proposals defeated:
FederatedIdentityNegotiationOrchestrator: composted
MigrationPipelineConfigDiffReconciler: composted
GraphAPIGroupResolutionFallbackService: composted
CredentialResolutionStrategyFactory: composted
BootstrapPermissionEscalationResolver: composted
ArtifactSeedingInfrastructureWithScopeHierarchy: composted
GroupMembershipVisualizationGraphWidget: composted
Squirrel compliments (involuntary): 1
("That's actually elegant")
Lizard scrolls: 4
Lizard non-scrolls (go:embed): 1
Douglas Adams references: at least 42
[A final scroll descended. It was longer than the others. It smelled of brass and Earl Grey and the particular satisfaction of a thing that works on the first try.]
THE GATE DOES NOT OPEN
FROM OUTSIDE
THE GATE OPENS
FROM INSIDE
THREE YAML LINES
ONE CREDENTIAL STORE
ONE LDAP FALLBACK
THE VOGONS BUILT
A THOUSAND FORMS
THE LIZARD CHANGED
THREE VALUES
PERMISSIONS ARE NOT CODE
PERMISSIONS ARE DATA
DATA BELONGS
IN THE UI
NOT IN A YAML FILE
ON A SERVER
NOBODY CAN REACH
🦎
P.S. - THE IDENTITY PROVIDER
HIDES YOUR GROUPS
WHEN YOU HAVE TOO MANY
THIS IS A "FEATURE"
NOT A BUG
ACCORDING TO THE PROVIDER
THE LIZARD DISAGREES
The gate opened from inside.
Not because it was hard to open —
because someone tried the handle.
Three lines of YAML.
Six minutes of testing.
One afternoon of making the admin
able to admin.
The Squirrel wanted seven frameworks
for seven problems that were one problem.
The Lizard changed three values
and the gate swung wide.
The chicken-and-egg unscrambled itself
when somebody put the permissions
where the permissions page could reach them.
The Vogons never found out.
They’re still filling forms.
🦎
🦎🔑🏰
See also:
The Solid Convergence:
- The Eyes That See — Where JARVIS learned to look through the window
- The Window That Opened Both Ways — Where markdown became the universal interface
- The Wallet We Gave — Where the agent got a budget and a dream
The Encyclopedia:
- Yagnipedia entries pending: “The Group Overage Problem”, “The Credential Store Pattern”
Storyline: The Solid Convergence
