Platform conventions¶
The de-facto standards that recur across every repository in the ecosystem. They did not come out of a formal specification — they are the rules that settled after the same real problems had been solved several times. A new platform should follow them from the start.
Identity casing¶
The rule: store all identity text in the database in lower case. Search is case-insensitive. Convert to the standard form for display (registration numbers upper case, names in Title Case).
Exempt fields — these are stored exactly as received:
| Field | Why |
|---|---|
etsi_identifier |
The format is defined by a standard |
| Certificate DN | Part of the cryptographic signature; must not be altered |
*_latin fields |
Latin transliteration — keeps its original form |
document_number |
An official document number |
| Hash values | Every bit is significant |
Where this rule came from
A citizen would register as АБ12345678 and later try to sign in as
аб12345678; the system saw a different person and a duplicate account
appeared. Normalising casing at the storage layer eliminates the entire class
of bug.
Role hierarchy¶
Hard rules:
| Role | Can do | CANNOT do |
|---|---|---|
superadmin |
Add/remove admin accounts | — |
admin |
Grant manager rights | Manage admin accounts |
manager |
Day-to-day operations | Add people |
user |
Their own actions | Grant permissions |
The super admin is a separate account with MFA. It is created through an onboarding wizard: invite allow-list → Google → eID → email OTP → TOTP + recovery codes. It is stored in its own table, keyed by Google identity.
The practical consequence: one person can be both an eID admin and a Google super admin — two separate accounts, two separate sign-ins. Every super admin sign-in is protected by MFA.
Admin rights are granted by registration number, against a user enrolled in the local eID.
Signature-backed permission grants¶
The rule: granting someone manager rights in an organisation sends that
person an eID SIGN push, and the grant only becomes ACTIVE once they have
approved it with PIN2.
Why: granting rights is an act with legal consequences. If an administrator makes someone a manager unilaterally, that person can later deny it. A PIN2 signature provides non-repudiation — it closes off the "I never agreed to this" defence.
Callback rules¶
The rule: a callback is only returned for same-device flows. In every other case, poll the session.
| Situation | Mechanism |
|---|---|
| User on a single device (deep link) | Callback |
| QR code — a second device | Session poll (long-poll) |
| Push notification | Session poll |
The reason: for a flow that started on another device, a callback has no idea where to return to. Polling makes the originating browser the source of truth and keeps the flow unambiguous.
Third-party deep links — passed through as a callback parameter; on completion the relevant app is brought to the foreground.
Google linking¶
The rule: before a Google account can be linked, the user must be verified by eID. The first link binds the account to a real person; afterwards signing in with Google directly is fine. It can also be unlinked.
The reason: anyone can create a Google account. On its own it cannot be accepted as citizen identification. Once bound through eID, that Google account points to a specific citizen.
RP ↔ rp_app¶
The rule: only the RP is registered with
eID. Multiple apps or subsystems under one RP
are passed through the rp_app / rp_app_url fields.
That way, logs and the user's own screen show which app made the request — without registering a separate RP per app and spreading credentials around.
Logout ends on the domain it started from¶
The rule: when a user signs out, they return to the domain they started
from — they are not thrown at the SSO's /login.
Why: the user was in the RP's app. Landing on the login screen of a completely unfamiliar domain afterwards is disorienting. Signing out should end within the app it began in.
Credentials have a single source¶
The rule: OAuth clients and client secrets are created only in the SSO and live only there. No other system creates, stores or displays them.
The Developer Portal is the test case for this rule: it provides guidance on registering an application but does not create clients, only deep-links into the SSO console.
Documentation is code¶
The rule: every repository has its own docs/ directory and MkDocs site.
Documentation lives in the same repo as the code and changes with it in the
same PR.
Language coverage: at minimum MN + EN; the main repositories add ZH · RU. See Internationalisation for the detail.
Clean Architecture — no back-imports¶
The rule: handler → usecase → repository → domain. Dependencies run in
one direction only. The business core (domain, usecase) never
imports a web framework.
An easy check: if a file under domain/ imports net/http or chi, the rule
has been broken.
Checklist for adding a new platform¶
- [ ] Authentication is via the SSO as an OIDC RP — no password system of its own
- [ ] Role hierarchy
superadmin → admin → manager → user - [ ] Identity text stored lower case in the database, exempt fields identified
- [ ] Postgres RLS enabled + boot-time enforceability guard
- [ ] Audit log — hash-chained
- [ ] Security headers, CORS allow-list and rate limits configured
- [ ]
/metricsand/swaggerclosed in production - [ ]
docs/+ a MkDocs site, MN/EN - [ ] CI: build + tests + a strict documentation check