# OSHUN BFF

Fastify TypeScript baseline for the OSHUN backend-for-frontend.

## Routes

- `GET /healthz` liveness probe
- `GET /readyz` readiness probe with normalized per-domain degradation summary
- `GET /home` home aggregator feed (highlights + continue strip)
- `GET /v1/home` compatibility alias for `GET /home`
- `GET /continue` resumable cross-domain items feed
- `GET /v1/continue` compatibility alias for `GET /continue`
- `GET /library` unified saved/favorites library feed
- `GET /v1/library` compatibility alias for `GET /library`
- `GET /domains` domain catalog with metadata, availability, scope flags, and
  entitlement access evaluations
- `GET /v1/domains` compatibility alias for `GET /domains`
- `GET /activity` unified cross-domain activity timeline feed
- `GET /v1/activity` compatibility alias for `GET /activity`
- `GET /search` unified cross-domain search feed
- `GET /v1/search` compatibility alias for `GET /search`
- `GET /search/suggestions` query suggestions + recent searches feed
- `GET /v1/search/suggestions` compatibility alias for `GET /search/suggestions`
- `GET /notifications` unified notifications feed
- `GET /v1/notifications` compatibility alias for `GET /notifications`
- `POST /notifications/:notificationId/read` mark a notification as read
- `POST /notifications/read/bulk` bulk mark notifications as read
- `POST /v1/notifications/:notificationId/read` compatibility alias
- `POST /v1/notifications/read/bulk` compatibility alias
- `GET /notifications/preferences` read per-domain notification preferences
- `PATCH /notifications/preferences/domains/:domainId` update per-domain
  notification preferences
- `GET /v1/notifications/preferences` compatibility alias
- `PATCH /v1/notifications/preferences/domains/:domainId` compatibility alias
- `GET /profile` authenticated shell profile envelope
- `GET /v1/profile` compatibility alias for `GET /profile`
- `PATCH /profile` update shell profile fields (`displayName`, `email`)
- `PATCH /v1/profile` compatibility alias for `PATCH /profile`
- `GET /preferences` authenticated shell preferences envelope
- `GET /v1/preferences` compatibility alias for `GET /preferences`
- `PATCH /preferences` update shell preferences (`theme`, `language`,
  notification toggles)
- `PATCH /v1/preferences` compatibility alias for `PATCH /preferences`
- `GET /nisaba/passages/daily` authenticated Nisaba daily passage envelope
- `GET /v1/nisaba/passages/daily` compatibility alias for
  `GET /nisaba/passages/daily`
- `GET /nisaba/passages/:passageId` Nisaba passage detail envelope
- `GET /v1/nisaba/passages/:passageId` compatibility alias
- `GET /nisaba/passages/:passageId/annotations` list passage annotations
- `GET /v1/nisaba/passages/:passageId/annotations` compatibility alias
- `POST /nisaba/passages/:passageId/annotations` create passage annotation
- `POST /v1/nisaba/passages/:passageId/annotations` compatibility alias
- `PATCH /nisaba/passages/:passageId/annotations/:annotationId` update passage
  annotation
- `PATCH /v1/nisaba/passages/:passageId/annotations/:annotationId` compatibility
  alias
- `GET /nisaba/passages/:passageId/compare` Nisaba compare workspace envelope
- `GET /v1/nisaba/passages/:passageId/compare` compatibility alias
- `GET /nisaba/notebooks` list Nisaba notebooks
- `GET /v1/nisaba/notebooks` compatibility alias for `GET /nisaba/notebooks`
- `POST /nisaba/notebooks` create Nisaba notebook
- `POST /v1/nisaba/notebooks` compatibility alias for `POST /nisaba/notebooks`
- `GET /nisaba/notebooks/:notebookId` Nisaba notebook detail envelope
- `GET /v1/nisaba/notebooks/:notebookId` compatibility alias
- `PATCH /nisaba/notebooks/:notebookId` update Nisaba notebook metadata
- `PATCH /v1/nisaba/notebooks/:notebookId` compatibility alias
- `POST /nisaba/notebooks/:notebookId/items` add a passage, annotation, grounded
  evidence view, or compare workspace to a notebook
- `POST /v1/nisaba/notebooks/:notebookId/items` compatibility alias
- `GET /nisaba/passages/:passageId/grounded-evidence` grounded evidence view
  envelope
- `GET /v1/nisaba/passages/:passageId/grounded-evidence` compatibility alias
- `GET /admin/workspaces` admin workspace summary feed
- `GET /v1/admin/workspaces` compatibility alias for `GET /admin/workspaces`
- `GET /admin/workspaces/:workspaceId` admin workspace detail envelope for
  `review | moderation | support | privacy | persona | model | rights | incident | policy | analytics`
- `GET /v1/admin/workspaces/:workspaceId` compatibility alias
- `POST /admin/review/items/:reviewId/assign` assign a review item
- `POST /admin/review/items/:reviewId/decision` record a review decision
- `POST /admin/moderation/queues/:queueId/simulations` create a moderation
  policy simulation
- `POST /admin/support/cases/:caseId/escalate` escalate a support case
- `POST /admin/privacy/requests/:requestId/resolve` resolve a privacy request
- `POST /admin/personas/:personaId/publish` publish a persona version
- `POST /admin/models/:modelId/promote` promote a model to staging or production
- `POST /admin/rights/requests/:requestId/resolve` resolve a rights request
- `POST /admin/incidents/:incidentId/mitigate` move an incident into monitoring
- `POST /admin/policies/:policyId/publish` publish a policy revision
- `POST /admin/analytics/reports` generate an analytics report snapshot
- `POST /v1/admin/...` compatibility aliases are available for each admin
  mutation route above
- `GET /v1/entitlements/domains/:domainId` evaluates domain access by
  entitlement context
- `GET /v1/domains/:domainId/bootstrap` protected domain bootstrap route
  (entitlement middleware)
- `GET /api/v6/catalog` V6 Egbe catalog, client tiers, world modes, and contract
  registry
- `POST /api/v6/tier-router/route` V6 native / Pixel Streaming / web fallback
  tier routing
- `GET /api/v6/steward/app` Egbe Steward App bootstrap envelope
- `POST /api/v6/steward/objectives` create or replace a remote steward objective
  using the canonical V6 `Objective` contract
- `POST /api/v6/steward/crossroads/:crossroadsId/counsel` record steward counsel
  for a V6 `Crossroads` contract
- `GET /api/v6/homestead` account-bound homestead state
- `PATCH /api/v6/homestead/settings` update homestead rest pace and world-mode
  settings
- `POST /api/v6/homestead/visit-invites` create a co-op homestead visit invite
- `GET /api/v6/account/bridge` read the V1 account to V6 steward bridge
- `PATCH /api/v6/account/preferences` update V6 bridge preferences

## Feed Pagination, Cache, and Partial-Failure Behavior

- Feed routes support cursor pagination:
  - `GET /home`: `highlightsLimit`, `highlightsCursor`, `continueLimit`,
    `continueCursor`, `favoritesLimit`, `favoritesCursor`
  - `GET /continue`, `GET /domains`, `GET /activity`, `GET /search`,
    `GET /library`, `GET /notifications`, `GET /search/suggestions`: `limit`,
    `cursor`
- `GET /library` supports `domain=all|tara|veritas|nyx|arete|nisaba|metis`
- Notifications filters: `inbox=all|unread|priority`,
  `domain=all|tara|veritas|nyx|arete|nisaba`
- Notifications feeds are filtered by per-domain preference state (`enabled`,
  `priorityOnly`, and `kinds` toggles).
- Notifications feeds deduplicate semantic cross-domain duplicates (same
  normalized title/body in a shared time window) and keep the highest-signal
  candidate.
- Responses include pagination envelopes, a `partialFailure` boolean, and a
  `trace` envelope with the current request correlation ID, route, normalized
  failure count, degraded domains, and degraded stages.
- Partial-response aware routes also emit:
  - `x-oshun-partial-response: true|false`
  - `x-oshun-partial-failure-count: <n>`
  - `x-oshun-degraded-domains: tara,veritas,...` when degradation is present
  - `x-oshun-degraded-stages: continue,highlight,...` when stage-aware
    degradation is present
- Feed routes apply short-lived private response caching with
  `x-oshun-cache: hit|miss` and `cache-control` headers.
- Profile and preferences routes return revisioned envelopes that can be cached
  client-side and patched incrementally.
- Nisaba passage, annotations, compare, notebook, and grounded-evidence reads
  also emit revisioned envelopes with short-lived private cache headers.
- Nisaba compare target launch metadata now resolves directly to compare
  workspaces instead of target passage detail pages.
- Nisaba annotation and notebook mutations validate linked passage, notebook,
  and grounded-evidence references before mutating per-user state.

## Search Ranking Strategy

- Search ranking uses a weighted lexical + intent + kind model.
- Reference: `docs/releases/v1/specs/search-ranking-strategy.md`

## Search Telemetry

- BFF emits typed analytics events for search:
  - `search_executed`
  - `search_zero_results` (only when query is non-empty and no matches)
- Telemetry is emitted for both `/search` and `/search/suggestions` responses.

## Domain API Adapters

- Typed adapter factory: `src/adapters/domain-service-adapters.ts`
- Includes strongly-typed clients for Tara, Veritas, Nyx, and Arete.
- Adapter contract covers health checks plus typed home/continue primitives for
  aggregator endpoints.
- `getHealth()` normalizes heterogeneous upstream health/readiness payloads into
  one shell-facing model: `ok | degraded | down`.
- Active adapters now probe domain-specific readiness or liveness paths instead
  of assuming one shared `/healthz` endpoint.

## Domain Health and Readiness

- BFF liveness remains `GET /healthz`.
- BFF readiness returns `status: ready | degraded | not_ready` plus:
  - `ready`: whether the shell should remain in rotation
  - `domains`: normalized Tara/Veritas/Nyx/Arete health statuses
  - `summary`: per-status counts plus impacted/down domain lists
- Active probe paths:
  - Tara: `/ready`
  - Veritas: `/readyz`
  - Nyx: `/health`
  - Arete: `/v1/arete/healthz`
- Health probes follow a shared retry and circuit-breaker policy:
  - timeout `2000ms`
  - `1` retry for retryable probe failures
  - circuit breaker threshold `5`
  - circuit reset timeout `30000ms`
  - half-open max `3`

## Domain Service Discovery

- Active adapter upstreams are configured through:
  - `OSHUN_TARA_API_BASE_URL`
  - `OSHUN_VERITAS_API_BASE_URL`
  - `OSHUN_NYX_API_BASE_URL`
  - `OSHUN_ARETE_API_BASE_URL`
- Residency data-plane overrides use the same variables with an uppercase zone
  suffix, for example `OSHUN_TARA_API_BASE_URL_EU`,
  `OSHUN_VERITAS_API_BASE_URL_US`, or `OSHUN_ARETE_API_BASE_URL_APAC`. When an
  authenticated request carries a `homeZone` claim, the BFF routes downstream
  domain calls to the user's home zone. A request only routes to a different
  `X-Oshun-Residency-Target-Zone` when the customer has an active explicit
  `data_processing` consent grant.
- The older `TARA_API_URL`, `VERITAS_API_URL`, `NYX_API_URL`, and
  `ARETE_API_URL` names are still accepted as compatibility aliases, but new
  environments should use the `OSHUN_*` variables.
- In `development` and `test`, the BFF falls back to canonical local adapter
  ports:
  - Tara `http://localhost:4101`
  - Veritas `http://localhost:4201`
  - Nyx `http://localhost:4301`
  - Arete `http://localhost:4401`
- In `staging` and `production`, the BFF fails fast unless each upstream origin
  is set explicitly. Use `OSHUN_RUNTIME_ENV=staging|production` to enable
  non-development routing policy.
- `OSHUN_DEPLOYMENT_RESIDENCY_ZONE` sets the deployment's default target zone.
  It defaults to the canonical residency policy home zone (`us`).

## OpenAPI

- Source spec: `openapi/oshun-bff.openapi.yaml`
- Consumer contract notes: `../web/docs/bff-api-contract.md`
- Admin contract notes: `../web/docs/admin-bff-api-contract.md`

## Integration Coverage

- `pnpm test:integration` runs the BFF-local integration harness in
  `vitest.integration.config.ts`.
- `src/__tests__/shell-domain-connectivity.integration.test.ts` validates
  end-to-end HTTP connectivity from shell requests into the BFF and out through
  the live Tara, Veritas, Nyx, and Arete adapters for readiness, home, and
  continue flows.

## Entitlement Headers

- `x-oshun-tier`: `free | pro | premium`
- `x-oshun-suspended-domains`: comma-separated domain ids (for temporary
  suspensions)
- `x-oshun-domain-tier-overrides`: comma-separated `domain:tier` overrides (for
  support/testing)

## Device Integrity Headers (High-Risk Routes)

- `x-oshun-device-platform`: `ios | android | web`
- `x-oshun-device-integrity`: `verified | browser_verified`
- `x-oshun-device-attested-at`: ISO-8601 attestation timestamp (must be fresh)

## Abuse Protection

- Entitlement and bootstrap routes apply in-memory rate limiting to reduce
  brute-force abuse.
- Exceeded limits return `429 Too Many Requests` with `retry-after` header.

## Request Tracing

- All routes support correlation propagation via `x-correlation-id`.
- If absent, BFF issues a request-scoped correlation id and returns it in the
  response header.
- Completion logs include correlation id, route, status code, and duration.

## Auth and Domain Authorization

- Protected domain routes require `Authorization: Bearer <token>`.
- Local/test development token format: `dev.<base64url-json-payload>`.
  - Payload shape:
    `{ "sub": "user-id", "scopes": ["domain:*" | "domain:tara" | ...], "exp": <unix-seconds> }`
- Production (`NODE_ENV=production`, `OSHUN_ENV=production`, or
  `RUNTIME_ENV=production`) rejects unsigned `dev.` tokens. Set
  `OSHUN_BFF_JWT_SECRET` to a 32+ character HMAC secret and issue signed HS256
  JWTs with issuer `OSHUN_BFF_JWT_ISSUER` (default `oshun-auth`) and audience
  `OSHUN_BFF_JWT_AUDIENCE` (default `oshun-bff`).
- Domain-scoped authorization requires either `domain:*` or `domain:{domainId}`
  scope.
- Admin workspace routes require one of:
  - `admin:*`
  - `admin:studio`
  - `admin:workspace:{workspaceId}`
- Home aggregation requires at least one domain scope and only queries
  authorized domains.
- Nisaba reader routes specifically require `domain:*` or `domain:nisaba`.

### Signup verification deployment settings

- `OSHUN_SIGNUP_VERIFICATION_HMAC_SECRET` is required when the durable admin
  snapshot database is configured. It must contain at least 32 bytes and must
  remain stable across deploys; the BFF fails startup if it is missing or does
  not match the stored snapshot.
- `OSHUN_SIGNUP_EMAIL_DELIVERY_URL` selects the transactional-mail relay;
  `OSHUN_SIGNUP_EMAIL_DELIVERY_TOKEN` is its optional bearer token. The relay
  must honor the supplied `Idempotency-Key`/`requestId` and return a stable
  `messageId` for retries.
- `OSHUN_SIGNUP_CHALLENGE_PROVIDER` and `OSHUN_SIGNUP_CHALLENGE_SITE_KEY` bind
  the challenge provider. Missing challenge configuration fails the escalation
  tier closed.
- Verification evidence is retained for at most 24 hours, resend timestamps for
  one hour, and velocity observations for 24 hours. Raw codes, network prefixes,
  and device fingerprints are never written to the snapshot.

## Commands

- `pnpm nx run @oshun/bff:dev`
- `pnpm nx run @oshun/bff:build`
- `pnpm nx run @oshun/bff:start`
- `pnpm nx run @oshun/bff:lint`
- `pnpm nx run @oshun/bff:typecheck`
- `pnpm nx run @oshun/bff:test`
