llms.txt
Rama main · réplica de lectura del repositorio original.
# Janua
Compact LLM context index for this repository.
## Read first
- `AGENTS.md` — canonical agent operating instructions.
- `README.md` — human/product overview when present.
- `ECOSYSTEM.md` — MADFAM ecosystem and Enclii deployment context when present.
- `llms-full.txt` — full context map and durable operating contract.
- `packages/cli/README.md` — `@janua/cli` consumer provisioning (`janua provision *`).
- `docs/PP_3B_STAGING_PIPELINE.md` — staging/prod pipeline, ops bootstrap, OAuth provisioning.
- `examples/consumer-bootstrap/` — `janua.client.yaml` template for consumer repos.
- `docs/EMAIL_SENDER_POLICY.md` — who our email comes from. THE RULE (a client
display name never pairs with a MADFAM address), the vCTO gate, and how a
tenant sends on its own provider account.
- `docs/MADFAM_EMAIL_INTEGRATION.md` — the internal email API surface: the two
send endpoints (`POST /send` raw, `POST /send-template` from the registry),
the template registry, the naive-vs-Jinja render split, and sender resolution.
The registry itself is `EMAIL_TEMPLATES` + `TEMPLATE_FILENAMES` in
`apps/api/app/routers/v1/email.py`, and `GET /templates` returns it live.
- `apps/api/app/mcp/README.md` — the janua **MCP server** (P1 pilot slice):
the transactional-email endpoints exposed as typed MCP tools, generated from
janua's OpenAPI, calling the HTTP API behind the same `X-Internal-API-Key`
gate (no back door). How to run it (`python -m app.mcp`), the generator
mapping, the auth model, and the drift guard tying tools to the schema.
- `docs/architecture/SILENT_SSO_SESSION.md` — one browser session across the
estate: the `janua_sso` cookie, `prompt=none`, and the hosted hop for hosts
outside the cookie domain.
- `docs/runbooks/oauth-shared-state-redis.md` — the hub for OAuth consent
state (Redis only; `503` + `Retry-After` when Redis is down, `403` only for a
real rejection), first-party clients, the account chooser, what each
sign-out / password change / password reset / `DELETE /sessions` revokes,
RFC 7009 `/oauth/revoke` (client authentication required), passkeys, and
readiness that reports Redis and the database without gating on them.
- `docs/runbooks/resend-domain-onboarding.md` — onboarding a client sending
domain, and migrating a client to their own Resend account.
- `docs/runbooks/resend-email-events.md` — the signed Resend webhook receiver,
the per-app events feed, the never-track-token-links rule, and first-party
open/click measurement on per-tenant tracking hosts.
- `docs/runbooks/secrets/resend-webhook-secret-rotation.md` — the per-account
webhook signing secret: Vault → ExternalSecret → env, rotating it without
exposing it, and reading 404 vs 401.
- `docs/reference/ISSUER_AND_JWKS.md` — the token verification contract other
services link to: issuer selection (`JANUA_CUSTOM_DOMAIN` else `API_BASE_URL`),
JWKS at `<issuer>/.well-known/jwks.json` with one RS256 key, `kid` rotation as
a hard cut, and `aud` per token type (ID token = `client_id`; access/service
token = the client's registered audience).
- `docs/runbooks/data-subject-request-access-audit.md` — data-subject request
exports are readable only by the subject or a platform admin (others get the
unknown-id 404), and the read-only audit
`apps/api/scripts/audit_data_subject_request_access.py`.
- `docs/runbooks/ALEMBIC_CONVERGENCE.md` — production migrations are applied
by hand and recorded in `apps/api/alembic/PROD_ALEMBIC_STATE.json`, which the
promote guard reads.
## Current state worth knowing (2026-09-07)
- Magic links to a host **outside** `COOKIE_DOMAIN` take a **hosted hop**: janua
renders a branded interstitial and forwards `?token=<access_token>` to the
product's `redirect_url`. The product must land that on a route that completes
a session from an access token, using `@madfam/janua-next@0.3.0` or later.
- The magic-link callback `GET` renders and **spends nothing**; only `POST`
exchanges. Mail scanners no longer burn links.
- Sending identity is **per tenant** (`app/services/sender_binding.py`), not one
global From line. A vCTO client tenant can send from its own domain on **its own
Resend account**, keyed by the env var its binding names in `credential_ref`.
A missing tenant credential degrades the sender to the platform From line; it
never blocks a sign-in link.
- A tenant display name is never paired with an address on MADFAM's domain.
## Sessions, revocation and readiness (2026-10-05)
- Revocation reads fail closed: while Redis cannot answer, refresh, session
check, the OIDC refresh grant, introspection, userinfo, passkeys and social
sign-in answer `503` + `Retry-After`. JWKS and discovery never touch Redis.
- Every revocation goes through `AuthService.revoke_sessions` (row flags,
refresh-token family, access JTI). `/auth/refresh` also refuses a refresh
token whose `sessions` row is not live. Password reset revokes every
session; password change keeps the current one; `sign-out-all` signs out
the accounts held in this browser only.
- Most routes (`get_current_user`) do not read the revocation list: a revoked
session's access token works until `exp`. Owner ruling pending.
- `GET /api/v1/health/ready` answers `200` with `"status": "degraded"` when
Redis or the database is down; alerting must read the body. Health bodies
carry status fields only.
- Passkey binary values are base64url; use the SDK's `WebAuthnHelper` or
`base64UrlToArrayBuffer` / `arrayBufferToBase64Url`, never bare `atob`.
## Email events and measurement (2026-09-26)
- Resend webhooks land on `POST /api/v1/email/webhooks/resend/{cuenta}` (one
slug per Resend account: `platform` plus one per tenant account, mapped in
`WEBHOOK_SECRET_SETTINGS`, `app/services/email_events.py`), verified
Svix-style with `RESEND_WEBHOOK_SECRET_<CUENTA>`. Secret unset: 404. Bad
signature: 401 «Invalid webhook signature». Stored: 200 `{"status":"stored"}`.
- Janua measures opens/clicks itself (Resend tracking stays OFF) only for mail
sent with `track_engagement: true` that carries no token link. Pixel
`/e/o/{token}.gif`, click `/e/c/{token}/{i}`, on the tenant's tracking host.
The setting a binding names in `tracking_host_setting` is that tenant's
on-switch (production sets a host on the tenant's own domain).
- A tracking host serves only `/e/o/*` and `/e/c/*`; everything else is 404
and websockets are closed (1008) by `TrackingHostScopeMiddleware`.
- Production migrations are applied by hand; `PROD_ALEMBIC_STATE.json` records
them (at `019_email_first_party_engagement` since 2026-09-25).
## Build and CI facts (2026-10-01)
- GitHub-hosted jobs run on `ubuntu-24.04` (#675); ARC jobs fall back to it.
- `@janua/typescript-sdk` >= 0.1.5 starts no timers or sockets without a
browser `window`, so a `JanuaClient` per server render is safe; earlier 0.1.x
leaked one interval per server render (pass `autoRefreshTokens: false` on the
server there). SDKs publish via `publish-sdks.yml` (tag `<dir>-v<version>`,
`pnpm publish`); publish `typescript-sdk` and `ui` before framework SDKs.
- `docker-publish.yml` does not rebuild the **docs** image on `pnpm-lock.yaml`
or `packages/**` changes. Ship docs with `gh workflow run docker-publish.yml`
(a dispatch rebuilds all five services).
- Pending work (pnpm 10 in the app images, the skip sweep, the skipped auth
e2e tests, the audit-log merge window and more) lives in one list:
AGENTS.md → «Known gaps / Backlog».
## Production operations
Routine production operations are Enclii-first. Use Enclii web, API, or CLI
before raw infrastructure tools. Direct `kubectl`, `helm`, SSH, provider CLIs,
`docker exec`, or direct container access is bootstrap/break-glass only.