Web PKI manager: CA hierarchies, X.509 issue/renew/revoke, CRLs, and a cert-manager external issuer for Kubernetes. Private keys live in a Cosmian KMS; metadata in SQLite.
Golden rule: NEVER edit task files directly — always use the backlog CLI.
(AGENTS.md is the auto-generated long-form of these rules.)
backlog task <id> --plain # view (--plain = AI output)
backlog task list --plain # list
backlog search "kw" --plain # search
backlog task create "Title" -d "Desc" --ac "Criterion"
backlog task edit <id> -s "In Progress" -a @myself # start
backlog task edit <id> --plan $'1. Step\n2. Step' # plan (ANSI-C quoting for newlines)
backlog task edit <id> --check-ac 1 # tick an AC
backlog task edit <id> --notes "PR description" -s Done # finishWorkflow: start → --plan → implement + --check-ac <i> → --notes → -s Done.
ACs must be outcome-oriented and testable ("User can log in"), not implementation steps.
| Action | Flag |
|---|---|
| Add / check / remove AC | --ac "..." / --check-ac <i> / --remove-ac <i> |
| Title / desc / status / assignee | -t / -d / -s / -a |
| Labels | -l a,b |
pnpm monorepo (workspaces frontend, backend) + a standalone Go module + infra dirs.
backend/ Fastify: tRPC + REST/OpenAPI API, crypto, KMS, SQLite → backend/CLAUDE.md
frontend/ React 19 SPA: TanStack Router/Query + tRPC + OIDC → frontend/CLAUDE.md
k8s/issuer/ Go cert-manager external issuer (own go.mod, Helm) → k8s/issuer/CLAUDE.md
docker/ Full-stack Compose + Dockerfile → DEPLOYMENT.md
keycloak/ Keycloak dev IdP + realm import → KEYCLOAK.md
kms/ Cosmian KMS dev stack → kms/README.md
ansible/ SSH host-cert deploy role → ansible/README.md
docs/ssh/ SSH onboarding guides (concept/quickstart/setup) → docs/ssh/concept.md
tests/ Playwright E2E (auth, RBAC, screenshots)
backlog/ Backlog.md tasks / docs / decisions
Per-directory guides: backend · frontend · k8s/issuer. Read the relevant one for verified commands, architecture, and gotchas before working in that subsystem.
React SPA ──tRPC──┐ Keycloak (OIDC) ── bearer JWT
▼
Backend (Fastify): /trpc · /api/v1 (REST+Swagger) · /api/v1/external (cluster tokens)
└ services/ → crypto (node-forge) · Cosmian KMS (KMIP/HTTP, private keys) · SQLite (Drizzle)
k8s/issuer (Go) ── POST /api/v1/external/sign ──► Backend (one cluster token = one CA)
The services/ layer is exposed over two APIs: typed tRPC (/trpc, used by the
frontend) and REST/OpenAPI (/api/v1, Swagger at /api/docs), plus a cluster-token
external-issuer API. OIDC is optional — with no OIDC_ISSUER/OIDC_AUDIENCE set, the
backend runs fully unauthenticated.
| Area | Stack |
|---|---|
| Backend | Fastify 5 · tRPC v11 · Drizzle + better-sqlite3 · Zod · node-forge · jose · Vitest |
| Frontend | React 19 · TanStack Router/Query · tRPC client · Tailwind 4 · Vite 7 · Vitest+RTL |
| Issuer | Go 1.23 · controller-runtime 0.20 · cert-manager 1.16 · Helm |
| Tooling | pnpm workspaces (Node ≥20, pnpm ≥9) · Commitizen conventional commits |
| Command | Action |
|---|---|
pnpm dev |
backend + frontend dev servers (--parallel -r) |
pnpm build / test / typecheck / lint |
fan out to both workspaces (-r) |
pnpm test:screenshots |
Playwright tests/screenshots.spec.ts |
Per-workspace commands (DB migrations, ports, build modes) live in the subsystem guides;
the Go issuer uses make from k8s/issuer/.
cd kms && docker compose up -d # Cosmian KMS (:42998)
cd keycloak && docker compose up -d # Keycloak (:42997, admin/admin)
pnpm install
cp backend/.env.example backend/.env && cp frontend/.env.example frontend/.env
cd backend && pnpm db:migrate
pnpm dev # backend :3000 + frontendThe committed dev config targets *.ymbihq.local on the 42xxx/52xxx port range — see
frontend/CLAUDE.md and edit the .env files for local-only dev.
Details: DEVELOPMENT.md (dev), DEPLOYMENT.md (Docker),
KEYCLOAK.md / OIDC.md (auth).
- New endpoint: Zod schema →
services/*method → expose via a tRPC procedure and/or REST route (both delegate to the service). - New frontend route: add
frontend/src/routes/<name>.tsx; the router plugin regeneratesrouteTree.gen.ts. - DB change: edit
backend/src/db/schema.ts→pnpm db:generate && pnpm db:migrate. - Write an
audit_logrow for every state-changing operation (success and failure).
- Types:
server,client,email,code_signing(schema also allowsdual) - Required subject: CN, O, C (2-letter). Optional: OU, ST, L.
- SANs: DNS, IP, email.