IdentityXBackend
Coding Conventions
How IdentityX backend code is written — spec-first, features, envelope, authz.
Spec-first development
api-spec.ymlis the contract and the single source of truth. Every operation, schema, and error sentinel lives there.- Regenerate bindings after touching it:
just generate-oapi identityx(writesinternal/openapi/*.gen.go, not committed). - The embedded spec is served by the harness at
/docs/openapi.yml— this site's API reference is that file. - Keep the spec's
info.descriptioncurrent: devs read it as the API's landing page.
Feature layout
One directory per feature under internal/handlers/ (authn, projects, actors, api_keys, capabilities, oauth_providers, organizations, profiles, profile_schemas). Each feature owns its models, routes, and repos:
- routes are registered in
internal/app/router.go, wired to the strict-server bindings. - authz crosses
internal/authz—CheckProject,CheckOrg,CheckPlatform— never inline role lookups in handlers. - scopes (
x-scopein the spec, e.g.platform-only) are enforced as chain middleware; adding one is one spec line + one registry entry.
Response envelope
Every handler returns the fun.Response envelope — success in data, failures as an AppError:
// success
SetResult(ctx, w, r, payload)
// failure — sentinel maps to the HTTP status
SetError(ctx, w, r, errx.ErrNotFound, nil)- Use the shared
errxsentinels (ErrNotFound,ErrUnauthorized,ErrConflict, …) — never rawhttp.Errorwith ad-hoc bodies. - Never leak existence: owner-only resources return
404, not403. - Introspect-style flows must unwrap
data— reading the envelope root yields a zeroed identity (the00000000-…owner bug).
Authentication model
- Two credential kinds everywhere: Bearer JWT and
X-API-Key(see the API reference intro). - Svc actors are project users, not members — don't
GetMemberon them; key creation relies on the caller's adminCheckProject. - Key lifecycle (
internal/keys), token lifecycle (internal/tokens), and API-key auth (lib/go/api_keys) are shared modules — extend them, don't reimplement.
Database
- Schema in
db/, queries written forsqlc(sqlc.yaml) — generated code ininternal/sqlc. - Repos in
internal/reposown the query layer; handlers never touch sqlc directly. - Multi-row work that must be atomic goes in a transaction — e.g. project creation also provisions the project's keys in one tx.
Tests & quality
- Parity tests in
internal/app(e.g.parity_test.go) assert the router surface matches the spec — keep them green when adding routes. just identityx test(gotestsum),just identityx lint(golangci-lint).lib/go/testdbfor DB-backed tests.
Don't
- Don't reimplement HTTP serving, middleware, or telemetry — boot through
lib/go/httpserver. - Don't return the envelope root as the payload — consumers unwrap
data. - Don't store secrets in the spec or commit
.env.