Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions architecture/c4-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ System_Boundary(ai, "AI") {

System_Boundary(platform, "Platform / Governance") {
Container(platformInfra, "platform-infra", "kustomize + GH workflows", "Build, push, deploy")
Container(contracts, "contracts-and-schemas", "OpenAPI + JSON Schema", "Source of truth")
Container(sdk, "shared-sdk-client", "TS/Python/Kotlin", "Generated clients")
Container(contracts, "contracts-and-schemas", "OpenAPI + JSON Schema", "Hand-written docs validated by CI; no code generation")
Container(sdk, "shared-sdk-client", "TypeScript", "Internal TypeScript library (shared schemas, HTTP proxy, game runtime)")
Container(secretsRepo, "secrets", "GitOps repo", "Sealed material")
Container(obs, "observability-platform", "Prometheus/Grafana stack", "Telemetry + alerts")
}
Expand All @@ -87,17 +87,16 @@ Rel(bffBack, quiz, "HTTP")
Rel(bffBack, wp, "HTTP")
Rel(bffBack, aiStats, "HTTP")
Rel(bffBack, gateway, "HTTP (control plane mutations)")
Rel(quiz, aiApi, "HTTP (generation)")
Rel(wp, aiApi, "HTTP (generation)")
Rel(quiz, gateway, "HTTP (generation, /internal/ai-engine)")
Rel(wp, gateway, "HTTP (generation, /internal/ai-engine)")
Rel(aiApi, llama, "HTTP")
Rel(aiApi, redis, "TCP")
Rel(aiStats, redis, "TCP")
Rel(users, usersDb, "TCP")
Rel(quiz, quizDb, "TCP")
Rel(wp, wpDb, "TCP")
Rel(platformInfra, gateway, "deploys")
Rel(contracts, sdk, "feeds")
Rel(sdk, gateway, "imports")
Rel(sdk, gateway, "imports (also both BFFs, users, quizz, wordpass)")
Rel(sdk, bffMobile, "imports")
Rel(sdk, bffBack, "imports")
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Multiple services and channels exchange request/response payloads. Without a sin

CI enforces the chain: contracts validated -> SDK regenerated -> consumers compiled against new SDK.

> **Status note (2026-11-12): partially implemented.** Contracts are validated in CI, but nothing regenerates the SDK: `shared-sdk-client` is a hand-written internal TypeScript library and there are no Python or Kotlin clients. The OpenAPI files cover the internal services only (not the gateway or the BFFs), and the event schemas have no producer or consumer. Treat the chain above as the intended direction, not as the current behaviour.

## Alternatives considered

1. Per-service schemas duplicated locally: rejected; produced silent drift historically.
Expand Down
12 changes: 7 additions & 5 deletions architecture/domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,14 @@ classDiagram

## Cross-context contracts

| Contract | Producer | Consumer | Source |
> Updated 2026-11-12: the Zod schemas below are defined in `shared-sdk-client/typescript/src/game-schemas.ts` and imported by the services. The JSON Schema and OpenAPI copies in `contracts-and-schemas` are hand-written documentation; nothing is generated from them.

| Contract | Defined in (code) | Consumer | Documented in |
|---|---|---|---|
| `RandomGameQuerySchema` | `contracts-and-schemas` | `api-gateway`, `bff-mobile`, `microservice-{quizz,wordpass}` | shared SDK |
| `LeaderboardQuerySchema` | `contracts-and-schemas` | `api-gateway`, `bff-mobile`, `microservice-users` | shared SDK |
| `GameCategoriesSchema` | `contracts-and-schemas` (json) | `shared-sdk-client/typescript`, gameplay services | generated at SDK build |
| AI generation request | `contracts-and-schemas/openapi/ai-engine` | quiz/wordpass, bff-backoffice | generated client |
| `RandomGameQuerySchema` | `shared-sdk-client` (`game-schemas.ts`) | `api-gateway`, `bff-mobile`, `microservice-{quizz,wordpass}` | `contracts-and-schemas/schemas/json/random-game.query.v1.json` |
| `LeaderboardQuerySchema` | `shared-sdk-client` (`game-schemas.ts`) | `api-gateway`, `bff-mobile`, `microservice-users` | `contracts-and-schemas/schemas/json/leaderboard.query.v1.json` |
| `GameCategoriesSchema` | `shared-sdk-client` (`game-schemas.ts`) | gameplay services | `contracts-and-schemas/schemas/json/game-categories.v1.json` |
| AI generation request | `ai-engine` (FastAPI app) and `shared-sdk-client` `ai-engine-client.ts` | quiz/wordpass (through the gateway) | `contracts-and-schemas/schemas/openapi/internal-ai-engine.v1.yaml` (partial) |

## Anti-patterns explicitly rejected

Expand Down
4 changes: 2 additions & 2 deletions architecture/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Common terms used across AxiomNode documentation.
| Sealed-secret | Encrypted secret object committed to git and decrypted in-cluster. |
| Validation chain | Per-repo CI gates (lint, test, audit, contract validation) that must pass before dispatch. |
| Dispatch | Cross-repo `repository_dispatch` from a service to `platform-infra` to trigger build/push. |
| SDK | Generated client library distributed via `shared-sdk-client` (TS / Python / Kotlin). |
| Contract | OpenAPI/JSON Schema/event schema in `contracts-and-schemas`. |
| SDK | `shared-sdk-client`: an internal TypeScript library (shared Zod schemas, HTTP proxy helpers, game-service runtime). Not generated from contracts. |
| Contract | OpenAPI/JSON Schema/event schema in `contracts-and-schemas`. Hand-written and validated by CI; the event schemas are not implemented. |
| Bounded context | DDD term; one microservice = one context. |
| Aggregate | DDD term; consistency boundary inside a bounded context. |
| ADR | Architecture Decision Record (see `decision-records/`). |
213 changes: 133 additions & 80 deletions architecture/key-sequence-flows.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
# Key Sequence Flows

Last updated: 2026-04-22.
Last updated: 2026-11-12.

Sequence diagrams of the most important runtime flows. Each flow lists the use case it satisfies and the failure points to consider.

> **Verification (2026-11-12):** SF-01, SF-02, SF-04, SF-05, SF-06 and SF-07 were rewritten from the route registrations and handlers on `origin/main` of `api-gateway` (`23e067b`), `bff-mobile` (`a3a3fc5`), `bff-backoffice` (`eb4a4a7`), `microservice-quizz`, `microservice-word-pass`, `microservice-users`, `ai-engine` and `shared-sdk-client` (`typescript/src/game-routes.ts`). The earlier version (2026-04-22) described routes that do not exist (`/v1/games/quiz/random`, `/admin/routing/ai-engine`, `/rounds/:id/result`, ...). SF-03 was not re-verified here. Wherever a claim could not be confirmed in code, the flow says so.

## SF-01 Mobile player starts a quiz round (UC-01)

`GET /v1/mobile/games/quiz/random` only reads rows that are already stored. It never calls `ai-engine`: content is produced beforehand by the generation endpoints and the batch worker (SF-07). The word-pass flow is identical with `/v1/mobile/games/wordpass/random` and `microservice-wordpass`.

```mermaid
sequenceDiagram
autonumber
participant App as Mobile App
participant GW as api-gateway
participant BFF as bff-mobile
participant Q as microservice-quizz
participant AI as ai-engine-api
participant L as llama runtime
participant DB as quizz DB

App->>GW: GET /v1/games/quiz/random?...
GW->>GW: Validate RandomGameQuerySchema
GW->>BFF: forwardHttp(/quiz/random)
BFF->>Q: GET /random?categoryId=...
Q->>Q: select cached GameGeneration by difficulty (Prisma)
alt cache hit
Q-->>BFF: 200 generation payload
else cache miss
Q->>AI: POST /generate {category, difficulty}
AI->>AI: optimizer + RAG retrieve + cacheKey
AI->>L: POST /completion
L-->>AI: model output
AI-->>Q: 200 generation
Q->>Q: persist GameGeneration (difficultyPercentage materialized)
Q-->>BFF: 200 generation
end
BFF-->>GW: 200 normalized payload
App->>GW: GET /v1/mobile/games/quiz/random?categoryId=...&count=...&language=...
GW->>GW: validate query (MobileRandomQuerySchema), drop language
GW->>BFF: GET /v1/mobile/games/quiz/random (mobile circuit breaker)
BFF->>Q: GET /games/models/random?categoryId=...
Q->>DB: find stored GameGeneration rows (gameType quiz, status != pending_review)
DB-->>Q: candidate rows
Q->>Q: pick random models
Q-->>BFF: 200 {gameType, requested, returned, items}
BFF->>BFF: apply requested count to items
BFF-->>GW: 200
GW-->>App: 200
```

Failure points:
- Invalid query → 400 from gateway, never reaches BFF.
- AI cache miss + llama unreachable → 503 surfaced with correlation id.
- DB unreachable → 5xx, no partial writes.
- Invalid query: 400 from the gateway, never reaches the BFF.
- Nothing stored for the category: the service answers 200 with `returned: 0`; no AI call is made as a fallback.
- Upstream failure or open circuit breaker at the gateway: 503 `Service temporarily unavailable`.

Related: `POST /v1/mobile/games/quiz/generate` (and `/wordpass/generate`) is also served from the inventory. `bff-mobile` calls the same `GET /games/models/random` on the service, returns the first item as `generated`, and answers 502 `No game models available to build a playable game` when there is none. It does not trigger AI generation.

## SF-02 Backoffice operator changes the runtime AI target (UC-09)

## SF-02 Backoffice operator changes runtime AI target (UC-09)
There are two independent targets. This flow covers the llama target that `ai-engine-api` uses for generation. The gateway keeps its own ai-engine API/stats target, changed with `PUT /internal/admin/ai-engine/target` (guarded by `API_GATEWAY_ADMIN_TOKEN` when set); it is not changed by this flow.

```mermaid
sequenceDiagram
Expand All @@ -50,28 +50,30 @@ sequenceDiagram
participant GW as api-gateway
participant BFF as bff-backoffice
participant AI as ai-engine-api
participant PV as mounted PV

SPA->>GW: PATCH /admin/routing/ai-engine
GW->>BFF: forward
BFF->>BFF: authorize operator role
BFF->>GW: PATCH /admin/runtime-target {url}
GW->>PV: persist RoutingTarget(scope=gateway-ai)
GW-->>BFF: 200
opt also update llama
BFF->>AI: PATCH /runtime/llama-target
AI->>PV: persist RoutingTarget(scope=ai-llama)
AI-->>BFF: 200
end
BFF-->>SPA: 200 effective target snapshot
participant Store as llama target store

SPA->>GW: PUT /v1/backoffice/ai-engine/target {target}
GW->>GW: check edge token (EDGE_API_TOKEN, when configured)
GW->>BFF: PUT /v1/backoffice/ai-engine/target
BFF->>BFF: validate AiEngineTargetSchema
BFF->>AI: PUT /internal/admin/llama-target (x-api-key when configured)
AI->>Store: save override and apply it to the running generator
AI-->>BFF: 200 target payload
BFF->>BFF: clear upstream cache and breakers
BFF-->>GW: 200 effective target snapshot
GW-->>SPA: 200
```

`DELETE /v1/backoffice/ai-engine/target` follows the same path and resets the override to the environment value.

Failure points:
- PV write failure → revert in-memory state, return 5xx.
- Drift vs manifests is expected; runbook documents reconciliation.
- Invalid payload or a failed sync with `ai-engine-api`: the BFF answers 400 with the error message.
- Whether the override survives a pod restart depends on where `llama_target_store` writes in the deployment; this was not verified in this review.

## SF-03 CI/CD build and staging deploy (UC-12, UC-13)

> Status: thesis-era flow. On `origin/main` of `platform-infra`, `deploy.yaml` is manual-only (`workflow_dispatch`) because the k3s staging host no longer exists; only the build and push step still runs. The `workflow_run` trigger shown below is currently disabled. The rest of this flow was not re-verified.

```mermaid
sequenceDiagram
autonumber
Expand All @@ -97,7 +99,7 @@ Failure points:
- Image digest mismatch → deploy aborts.
- Smoke check fail → rollout marked unhealthy; manual intervention.

## SF-04 Operator inspects AI history with difficulty filter (UC-06)
## SF-04 Operator inspects stored quiz history with a difficulty filter (UC-06)

```mermaid
sequenceDiagram
Expand All @@ -108,77 +110,128 @@ sequenceDiagram
participant Q as microservice-quizz
participant DB as quizz DB

SPA->>GW: GET /admin/quiz/history?difficulty=hard&limit=...
GW->>BFF: forward
BFF->>BFF: cache lookup (TTL key normalized w/o querystring)
alt cache hit
BFF-->>SPA: 200 from cache
else cache miss
BFF->>Q: GET /historyPage?difficulty=...
Q->>DB: SELECT ... WHERE difficultyPercentage BETWEEN ...
SPA->>GW: GET /v1/backoffice/services/microservice-quiz/data?dataset=history&difficultyPercentage=...&page=...
GW->>GW: check edge token (EDGE_API_TOKEN, when configured)
GW->>BFF: GET /v1/backoffice/services/microservice-quiz/data
BFF->>BFF: validate DataQuerySchema
BFF->>BFF: upstream cache lookup (TTL depends on the upstream path)
alt cache miss
BFF->>Q: GET /games/history?limit=...&page=...&pageSize=...&difficultyPercentage=...
Q->>DB: query stored generations
DB-->>Q: rows
Q-->>BFF: 200
BFF->>BFF: store in metrics cache
BFF-->>SPA: 200
Q-->>BFF: 200 page of history
end
BFF->>BFF: apply filter/sort, compute history insights
BFF-->>GW: 200 {service, dataset, total, page, pageSize, rows, insights}
GW-->>SPA: 200
```

Failure points:
- DB slow query → BFF returns 504 after upstream timeout.
- Cache stampede → mitigated by short TTL + per-path key normalization.
- Invalid query: 400 from the BFF with the flattened validation errors.
- Slow or unreachable service: the BFF applies its upstream timeout and circuit breaker; the exact status mapping was not verified here.
- The service key for the quiz service is `microservice-quiz` (not `microservice-quizz`) in the BFF routes.

## SF-05 AI ingest / corpus refresh (UC-11)

There is no BFF route for ingestion. The entry point is the game service, which forwards to `ai-engine` through the gateway (see SF-07 for the same hop used by generation).

```mermaid
sequenceDiagram
autonumber
participant Op as Operator/Cron
participant Op as Internal caller
participant Q as microservice-quizz
participant GW as api-gateway
participant AI as ai-engine-api
participant Vec as vector store
participant Cache as Redis

Op->>AI: POST /ingest {corpus_id}
AI->>AI: load curated corpus
AI->>AI: compute embeddings (MiniLM-L12)
AI->>Vec: upsert vectors
AI->>AI: compute new corpusSignature
AI->>Cache: invalidate by signature prefix (logical)
AI-->>Op: 202 ingestion accepted

Op->>Q: POST /games/ingest {documents[], source?, categoryId?, difficultyPercentage?}
Q->>GW: POST /internal/ai-engine/ingest/quiz
GW->>AI: POST /ingest/quiz
AI->>AI: normalize metadata, pipeline.ingest(docs), refresh optimizer
AI-->>GW: 200 {ingested}
GW-->>Q: 200
Q-->>Op: 202 {gameType, ingested}
```

Failure points:
- Embedding model load failure → ingestion aborts, previous signature retained.
- Vector store partial write → ingestion marked failed; retry idempotent.
- Invalid payload: 400 from the service.
- `ai-engine` unavailable or RAG pipeline not initialised (503 from `ai-engine`): the service answers 502 `Failed to ingest documents into ai-engine RAG`.
- `ai-engine` also exposes the generic `POST /ingest` and `POST /ingest/word-pass`; `POST /ingest/true-false` exists but nothing calls it (see the true-false note below).

## SF-06 Player submits gameplay results (UC-03)

## SF-06 Player submits gameplay result (UC-03)
State on `origin/main`: results stay inside `bff-mobile`. `microservice-quizz` has no result endpoint and `microservice-users` is only used by the BFF to identify the player.

```mermaid
sequenceDiagram
autonumber
participant App as Mobile App
participant GW as api-gateway
participant BFF as bff-mobile
participant Q as microservice-quizz
participant U as microservice-users
participant PS as PlayerStore (BFF local DB)

App->>GW: POST /v1/mobile/games/events {events[]}
GW->>BFF: POST /v1/mobile/games/events
BFF->>U: GET /users/me/profile (resolve player identity from the token)
U-->>BFF: 200 profile
BFF->>BFF: validate GameEventsSyncSchema
BFF->>PS: saveGameEvents(playerId, events)
PS-->>BFF: synced count and stats
BFF-->>GW: 200 {synced, message, stats}
GW-->>App: 200
```

Failure points:
- No token or an invalid token: 401 from the BFF.
- `microservice-users` unreachable while resolving the identity: 502 `Failed to resolve player identity`.
- Consequence: mobile results are not written to `microservice-users`, so they do not reach its leaderboard. `microservice-users` has `POST /users/me/games/events` (and the open, unmerged PR AxiomNode/microservice-users#5 proposes `POST /users/me/games/events/batch`), but `bff-mobile` on main does not call either.

## SF-07 Content generation: batch worker and generation processes

Generation is separate from serving. The services call `ai-engine` through the gateway (`AI_ENGINE_BASE_URL=http://api-gateway:7005/internal/ai-engine` in `platform-infra/kubernetes/base/microservice-quizz/api-deployment.yaml`).

```mermaid
sequenceDiagram
autonumber
participant SPA as backoffice
participant GW as api-gateway
participant BFF as bff-backoffice
participant Q as microservice-quizz
participant AIGW as api-gateway /internal/ai-engine
participant AI as ai-engine-api
participant L as llama runtime
participant DB as quizz DB

App->>GW: POST /v1/games/quiz/result
SPA->>GW: POST /v1/backoffice/services/microservice-quiz/generation/worker/start {countPerIteration, categoryIds?, difficultyLevels?}
GW->>BFF: forward
BFF->>Q: POST /rounds/:id/result
Q->>Q: persist score
Q-->>BFF: 200
BFF->>U: POST /players/:id/events {gameResult}
U->>U: update PlayerStats projection
U-->>BFF: 202
BFF-->>App: 200
BFF->>Q: POST /games/generate/worker/start
Q-->>BFF: 200 {gameType, worker}
loop worker iterations (interval timer inside the service)
Q->>Q: runGenerationProcessBlocking for the selected categories and difficulty levels
Q->>AIGW: POST /internal/ai-engine/generate/quiz
AIGW->>AI: POST /generate/quiz
AI->>L: completion request (RAG context)
L-->>AI: model output
AI-->>AIGW: 200 generation
AIGW-->>Q: 200 generation
Q->>DB: persist GameGeneration
end
```

Failure points:
- Q write fails → no analytics call; gateway returns 5xx.
- U analytics call fails → considered eventually consistent; logged for replay.
Other entry points on the same service: `POST /games/generate` (one item, synchronous), `POST /games/generate/process` (asynchronous, returns a `taskId`; poll `GET /games/generate/process/:taskId`) and `POST /games/generate/process/wait` (blocking). The Backoffice exposes them as `/v1/backoffice/services/:service/generation/process`, `/wait`, `/processes`, `/process/:taskId`.

Notes:
- The k8s base manifest sets `BATCH_GENERATION_ENABLED=false` for `microservice-quizz`; the runtime worker above is controlled through the `/games/generate/worker*` routes.
- The worker is in-process state of each service. There is no queue or message broker.

## True-false is not a game of the platform

`ai-engine` has `POST /generate/true-false` and `POST /ingest/true-false`, but no microservice, BFF route or the mobile app uses them (the mobile `GameType` is QUIZ or WORDPASS only, per the F7 review notes; app code not re-checked here). The gateway does not proxy them either (it only proxies `generate/quiz`, `generate/word-pass`, `ingest/quiz`, `ingest/word-pass`, `catalogs`, `health` and `stats` under `/internal/ai-engine`).

## Related documents

- [Use cases](./use-cases.md)
- [Domain model](./domain-model.md)
- [Target architecture](./target-architecture.md)
- [Runtime routing](../operations/runtime-routing-and-service-targeting.md)
- Route inventory of the gateway and both BFFs (generated from code): `contracts-and-schemas/schemas/route-inventory/edge-routes.md`
Loading
Loading