30 KiB
Auth / Session Service Implementation Plan
This plan has been already implemented and stays here for historical reasons.
It should NOT be threated as source of truth for service functionality.
Purpose
This plan describes a detailed, incremental implementation path for
Auth / Session Service that integrates with the existing
Edge Gateway.
The plan is intentionally atomic. Each stage should be small enough to implement, review, and test without overloading development context.
Global Rules for the Entire Plan
- keep domain logic independent from concrete storage backends;
- keep gateway projection separate from source-of-truth records;
- preserve the existing public auth contract expected by gateway:
send-email-code->challenge_idconfirm-email-code->device_session_id
- keep
confirm-email-codesynchronous; - do not introduce a pending async session-provisioning model;
- use synchronous internal REST where immediate answer is required;
- use Redis Streams / pub-sub only for session lifecycle propagation and other event-style side effects;
- keep implementation idempotent where retries are expected;
- design Redis-backed stores behind interfaces so SQL migration remains possible.
Milestone Structure
Suggested milestones:
- Domain skeleton and ports
- In-memory service behavior and tests
- Redis-backed source-of-truth stores
- Gateway projection publisher
- Public HTTP API
- Internal trusted API
- Integration with user-service and config-provider ports
- Revoke/block flows
- Observability and hardening
- End-to-end integration with gateway
Suggested Module Structure
The structure described below is allowed to be changed during the Plan steps implementation.
authsession/
├── cmd/
│ └── authsession/
│ └── main.go
│
├── internal/
│ ├── app/
│ │ ├── app.go
│ │ ├── bootstrap.go
│ │ └── wiring.go
│ │
│ ├── config/
│ │ ├── config.go
│ │ ├── env.go
│ │ └── validation.go
│ │
│ ├── domain/
│ │ ├── challenge/
│ │ │ ├── model.go
│ │ │ ├── state.go
│ │ │ ├── policy.go
│ │ │ └── errors.go
│ │ │
│ │ ├── devicesession/
│ │ │ ├── model.go
│ │ │ ├── state.go
│ │ │ ├── revoke.go
│ │ │ └── errors.go
│ │ │
│ │ ├── userresolution/
│ │ │ ├── model.go
│ │ │ └── policy.go
│ │ │
│ │ ├── sessionlimit/
│ │ │ ├── model.go
│ │ │ └── policy.go
│ │ │
│ │ └── common/
│ │ ├── email.go
│ │ ├── time.go
│ │ ├── ids.go
│ │ └── types.go
│ │
│ ├── ports/
│ │ ├── challengestore.go
│ │ ├── sessionstore.go
│ │ ├── userdirectory.go
│ │ ├── configprovider.go
│ │ ├── mailsender.go
│ │ ├── projectionpublisher.go
│ │ ├── clock.go
│ │ ├── idgenerator.go
│ │ ├── codegenerator.go
│ │ └── codehasher.go
│ │
│ ├── service/
│ │ ├── sendemailcode/
│ │ │ └── service.go
│ │ ├── confirmemailcode/
│ │ │ └── service.go
│ │ ├── getsession/
│ │ │ └── service.go
│ │ ├── listusersessions/
│ │ │ └── service.go
│ │ ├── revokedevicesession/
│ │ │ └── service.go
│ │ ├── revokeallusersessions/
│ │ │ └── service.go
│ │ ├── blockuser/
│ │ │ └── service.go
│ │ └── shared/
│ │ ├── normalize.go
│ │ ├── projection.go
│ │ └── publicerrors.go
│ │
│ ├── api/
│ │ ├── publichttp/
│ │ │ ├── handler_send_email_code.go
│ │ │ ├── handler_confirm_email_code.go
│ │ │ ├── dto.go
│ │ │ └── errors.go
│ │ │
│ │ └── internalhttp/
│ │ ├── handler_get_session.go
│ │ ├── handler_list_user_sessions.go
│ │ ├── handler_revoke_device_session.go
│ │ ├── handler_revoke_all_user_sessions.go
│ │ ├── handler_block_user.go
│ │ ├── dto.go
│ │ └── errors.go
│ │
│ ├── adapters/
│ │ ├── redis/
│ │ │ ├── challengestore/
│ │ │ │ └── store.go
│ │ │ ├── sessionstore/
│ │ │ │ └── store.go
│ │ │ ├── configprovider/
│ │ │ │ └── provider.go
│ │ │ └── gatewayprojection/
│ │ │ ├── publisher.go
│ │ │ ├── snapshot.go
│ │ │ └── stream.go
│ │ │
│ │ ├── userservice/
│ │ │ ├── client.go
│ │ │ ├── mapper.go
│ │ │ └── stub.go
│ │ │
│ │ ├── mail/
│ │ │ ├── stub.go
│ │ │ └── rest_client.go
│ │ │
│ │ ├── crypto/
│ │ │ ├── codehasher.go
│ │ │ └── publickey.go
│ │ │
│ │ ├── clock/
│ │ │ └── system.go
│ │ │
│ │ └── id/
│ │ ├── challengeid.go
│ │ └── devicesessionid.go
│ │
│ ├── observability/
│ │ ├── logging.go
│ │ ├── metrics.go
│ │ └── tracing.go
│ │
│ └── testkit/
│ ├── fixtures.go
│ ├── fake_clock.go
│ ├── fake_idgen.go
│ ├── fake_mail.go
│ ├── fake_userdir.go
│ └── fake_projection.go
│
├── api/
│ ├── public-openapi.yaml
│ └── internal-openapi.yaml
│
└── README.md
Description
-
cmd/authsession— service entry point: process startup, configuration loading, application assembly, and HTTP server startup. -
internal/app— top-level application orchestration layer: dependency initialization, runtime bootstrap, and component wiring. -
internal/config— service configuration loading, normalization, and validation from environment and other sources. -
internal/domain/challenge— domain model for thesend_email_code/confirm_email_codechallenge flow: states, transitions, TTL/retry policies, and domain errors. -
internal/domain/devicesession— domain model fordevice_session: session state, revocation, revoke reasons, and related domain errors. -
internal/domain/userresolution— domain model for user resolution by email through user-service: existing user, allowed registration, or blocked user. -
internal/domain/sessionlimit— domain model and policy rules for activedevice_sessionlimits. -
internal/domain/common— shared domain value objects and helper types: email, time, identifiers, and common primitive types. -
internal/ports— interfaces for all external dependencies: source-of-truth stores, user-service, mail delivery, config, projection publisher, clock, generators, and hashing. -
internal/service/sendemailcode— use case for sending a code: email normalization, challenge lifecycle, suppression/send decision, and success-shaped public response. -
internal/service/confirmemailcode— use case for confirming a code: challenge validation, public-key validation, resolve/create user flow, session-limit enforcement,device_sessioncreation, and projection publication. -
internal/service/getsession— use case for reading a singledevice_sessionfor the trusted internal API. -
internal/service/listusersessions— use case for listing user sessions for the trusted internal API. -
internal/service/revokedevicesession— use case for revoking a single device session and publishing the updated gateway projection. -
internal/service/revokeallusersessions— use case for revoking all active sessions of a user and publishing the resulting updates. -
internal/service/blockuser— use case for blocking a user/email and revoking active sessions according to policy. -
internal/service/shared— shared application-layer code: normalization helpers, gateway projection builders, and public error mapping. -
internal/api/publichttp— public HTTP API for gateway integration: handlers, DTOs, and error mapping forsend_email_codeandconfirm_email_code. -
internal/api/internalhttp— trusted internal HTTP API: revoke/read/list/block endpoints, DTOs, and separate internal error policy. -
internal/adapters/redis/challengestore— Redis adapter for source-of-truth challenge storage. -
internal/adapters/redis/sessionstore— Redis adapter for source-of-truthdevice_sessionstorage. -
internal/adapters/redis/configprovider— Redis adapter for dynamic configuration, such as active-session limits. -
internal/adapters/redis/gatewayprojection— Redis adapter for theEdge Gatewayintegration projection: KV snapshots and lifecycle updates in streams. -
internal/adapters/userservice— user-service integration adapter: REST client, response-to-domain mapping, and stub implementation for early stages. -
internal/adapters/mail— mail-delivery adapter: development stub and future REST mail-service client. -
internal/adapters/crypto— cryptographic adapters: confirmation-code hashing andclient_public_keyvalidation/parsing. -
internal/adapters/clock— system clock implementation. -
internal/adapters/id— generation of stable domain identifiers such aschallenge_idanddevice_session_id. -
internal/observability— service logging, metrics, and tracing. -
internal/testkit— test fixtures, fake/mock dependencies, and shared helpers for unit and integration tests. -
api/public-openapi.yaml— formal specification of the public HTTP API. -
api/internal-openapi.yaml— formal specification of the trusted internal HTTP API. -
README.md— architectural service description covering its role in the system, contracts, domain rules, and integrations.
Stage 1. Freeze the Service Contract
Status: implemented.
Goal
Write down the exact service-level contracts before implementation starts.
Tasks
- freeze public auth use cases:
send_email_codeconfirm_email_code
- freeze internal trusted use cases:
GetSessionListUserSessionsRevokeDeviceSessionRevokeAllUserSessionsBlockUser
- define canonical request/response DTOs for the service boundary;
- define client-safe error classes for the public auth API;
- define richer internal error classes for logs and internal API.
Deliverables
- service contract notes in repo docs;
- initial error catalog;
- agreement on public vs internal API boundaries.
Exit Criteria
- no unresolved ambiguity around public auth input/output shapes;
- no unresolved ambiguity around internal revoke/read operations.
Stage 2. Define Core Domain Types
Status: implemented.
Goal
Create the minimal domain model without any transport or storage code.
Tasks
- define challenge aggregate concept;
- define device-session aggregate concept;
- define revoke reason model;
- define user resolution result model:
- existing user
- creatable user
- blocked user
- define session-limit decision model;
- define mail-delivery result model;
- define projection snapshot model for gateway integration;
- define domain statuses and allowed transitions.
Important Constraints
- challenge and session models must not depend on Redis-specific encoding;
- gateway projection model must be separate from domain entities.
Deliverables
- domain package with types only;
- transition invariants documented in code comments and tests.
Exit Criteria
- domain package compiles without storage adapters;
- status transitions are covered by unit tests.
Stage 3. Define Service Ports
Status: implemented.
Goal
Create clean interfaces around every external dependency.
Tasks
Define interfaces conceptually equivalent to:
ChallengeStoreSessionStoreUserDirectory/UserResolverConfigProviderMailSenderGatewaySessionProjectionPublisherClockIDGeneratorCodeGeneratorCodeHasher
Notes
ChallengeStoreandSessionStoreare source-of-truth ports;GatewaySessionProjectionPublisheris an integration port, not a domain store;UserDirectorymust support existing / creatable / blocked decisions and user creation when allowed;ConfigProvidermust support "limit absent" as a first-class case.
Deliverables
- interface package or packages;
- port-level test doubles.
Exit Criteria
- service layer can be implemented against interfaces only.
Stage 4. Implement Pure Domain Services In Memory
Status: implemented.
Goal
Implement the auth logic once, against in-memory stores and adapters.
Tasks
Implement core use cases:
SendEmailCodeConfirmEmailCodeGetSessionListUserSessionsRevokeDeviceSessionRevokeAllUserSessionsBlockUser
Required Behaviors
SendEmailCode
- normalize email;
- consult
UserDirectorypolicy if needed; - create challenge;
- generate secure code;
- store only hashed code;
- attempt delivery or suppress it;
- always return a success-shaped result with
challenge_id.
ConfirmEmailCode
- load challenge;
- validate expiration and status;
- validate code hash;
- validate
client_public_keyformat; - handle idempotent repeat confirm for same successful challenge and same key;
- resolve/create user through
UserDirectory; - reject blocked user;
- load session-limit config;
- count active sessions;
- reject if limit exceeded;
- create session;
- store session;
- move challenge into short-window confirmed state;
- publish session projection;
- return
device_session_id.
Revoke Flows
- update source of truth;
- publish revoked projection for every affected session.
Deliverables
- service layer with in-memory dependencies;
- unit tests for every public behavior.
Exit Criteria
- full service logic is testable without Redis or HTTP;
- edge cases are covered by unit tests.
Stage 5. Design Challenge Rules in Detail
Status: implemented.
Goal
Remove ambiguity from challenge handling before persistent adapters are written.
Tasks
- define challenge TTL;
- define max confirm attempts;
- define resend behavior policy, if any;
- define short idempotency window after successful confirm;
- define state machine for:
- new challenge
- sent/suppressed
- confirmed
- expired
- failed
- define exact behavior for repeated confirms:
- same code + same key -> same session id
- same code + different key -> fail
- expired challenge -> fail
- too many attempts -> fail
Deliverables
- explicit challenge policy spec in code comments/tests.
Exit Criteria
- no hidden challenge behavior remains undecided.
Stage 6. Define Public Error Policy
Status: implemented.
Goal
Make public auth failures predictable and safe.
Tasks
Decide exact client-safe categories for:
- malformed e-mail;
- malformed
client_public_key; - unknown challenge;
- expired challenge;
- invalid code;
- blocked by policy at confirm stage;
- session limit exceeded;
- temporarily unavailable.
Additional Rules
send_email_codemust not reveal whether the e-mail exists or is blocked;- public errors should be normalized for gateway passthrough;
- internal logs and traces may keep richer reasons.
Deliverables
- public error mapping table;
- internal error hierarchy.
Exit Criteria
- gateway adapter behavior can be implemented without guesswork.
Stage 7. Implement Redis ChallengeStore
Status: implemented.
Goal
Add the first persistent backend for challenges.
Tasks
- implement challenge read/write/update operations in Redis KV;
- define Redis key scheme for challenges;
- store hashed codes only;
- store challenge status and timestamps;
- support atomic compare-and-set style updates where required;
- support expiration cleanup through TTL and/or explicit status.
Important Design Rule
The interface must not expose Redis primitives directly.
Deliverables
- Redis-backed challenge store adapter;
- adapter integration tests against Redis.
Exit Criteria
- challenge lifecycle works against Redis under concurrent access assumptions.
Stage 8. Implement Redis SessionStore
Status: implemented.
Goal
Add the first persistent backend for sessions.
Tasks
- implement create/read/list/revoke operations;
- define Redis key scheme for sessions;
- support listing all sessions for one user;
- support revoking one session;
- support revoking all sessions for one user;
- support block-related session revocation;
- support active-session counting for limit enforcement;
- store revoke reason and actor metadata.
Important Design Rule
The session source-of-truth record must remain distinct from gateway projection encoding.
Deliverables
- Redis-backed session store adapter;
- adapter integration tests.
Exit Criteria
- all session lifecycle operations are persistent and testable.
Stage 9. Implement Redis ConfigProvider
Status: implemented.
Goal
Support dynamic session-limit configuration.
Tasks
- implement config lookup from Redis KV;
- define config key scheme for auth-service settings;
- support:
- limit present with integer value
- limit absent
- invalid config value
- define fallback behavior for invalid config read.
Required Behavior
- missing config -> no session-count limit;
- invalid config -> fail closed or fail safe according to explicit decision;
- document the chosen policy.
Deliverables
- Redis-backed config adapter;
- tests for absent, valid, and invalid values.
Exit Criteria
- session-limit logic no longer depends on hard-coded constants.
Stage 10. Implement Gateway Session Projection Publisher
Status: implemented.
Goal
Bridge auth source-of-truth state into gateway-facing cache/projection state.
Tasks
- define exact projection snapshot structure consumed by gateway;
- define Redis KV key scheme for gateway session lookup;
- define Redis Stream schema for session lifecycle updates;
- implement projection write on session create;
- implement projection update on session revoke;
- implement projection update for bulk revoke/all;
- make publication idempotent and retry-safe.
Important Constraints
- projection publisher should accept domain session data and transform it;
- it must not force domain logic to know Redis snapshot shape.
Deliverables
- Redis-backed projection publisher;
- integration tests that emulate gateway expectations.
Exit Criteria
- created sessions appear in gateway-readable projection;
- revoked sessions produce gateway-readable invalidation/update records.
Stage 11. Implement Stub MailSender
Status: implemented.
Goal
Introduce the mail-delivery port without coupling auth logic to one concrete delivery transport.
Tasks
- create a stub adapter with deterministic success/failure modes;
- record delivery attempts for tests;
- support explicit suppression mode for blocked/hidden flows;
- ensure service logic can distinguish:
- sent
- suppressed
- failed
Deliverables
- stub mail adapter;
- tests around challenge delivery state transitions.
Exit Criteria
- auth logic is fully testable without real mail infrastructure.
Stage 12. Implement Stub UserDirectory
Status: implemented.
Goal
Introduce the user-service dependency before its real service exists.
Tasks
- create an in-memory or stub REST-like adapter that can return:
- existing user
- creatable user
- blocked user
- support create-on-confirm behavior;
- support lookups by normalized email;
- support user block state.
Deliverables
- stub user-service adapter;
- integration tests for auth flows.
Exit Criteria
- auth-service no longer needs to fake user decisions internally.
Stage 13. Implement Public HTTP API
Status: implemented.
Goal
Expose the synchronous public auth flow expected by gateway.
Tasks
- create HTTP handlers for:
send_email_codeconfirm_email_code
- define JSON DTOs matching gateway expectations;
- implement request validation;
- implement response normalization;
- implement mapping from internal errors to public client-safe errors;
- add request timeout handling and structured logging.
Important Constraints
- keep semantics aligned with gateway adapter expectations;
- do not expose internal admin/session methods on the public listener.
Deliverables
- public HTTP server;
- handler tests;
- end-to-end tests through HTTP.
Exit Criteria
- gateway can call the service through a real HTTP adapter.
Stage 14. Implement Internal Trusted API
Status: implemented.
Goal
Expose lifecycle and read operations for trusted internal callers.
Tasks
Implement internal endpoints for:
GetSessionListUserSessionsRevokeDeviceSessionRevokeAllUserSessionsBlockUser
Optional additions later:
- unblock flow;
- challenge inspection.
Notes
- this may use REST for simplicity;
- authentication/authorization of internal callers can be stubbed initially if there is not yet a platform-wide internal auth mechanism.
Deliverables
- internal HTTP API;
- handler tests.
Exit Criteria
- session lifecycle can be driven without touching Redis manually.
Stage 15. Implement Revoke Logic Thoroughly
Status: implemented.
Goal
Make revoke behavior explicit and reliable.
Tasks
For RevokeDeviceSession:
- load target session;
- no-op or explicit result if already revoked;
- persist revoke metadata;
- publish revoked projection.
For RevokeAllUserSessions:
- list active sessions for user;
- revoke each relevant session;
- publish projection for each affected session;
- preserve reason metadata.
For BlockUser:
- mark user blocked through
UserDirectoryor trusted policy adapter; - revoke all active sessions;
- ensure future auth flow is denied at confirm stage and mail can be suppressed at send stage.
Deliverables
- complete revoke implementation;
- tests for single, bulk, and block flows.
Exit Criteria
- gateway-facing revoke propagation is available for all revoke models.
Stage 16. Add Consistency Safeguards
Status: implemented.
Goal
Reduce create/revoke drift between source of truth and gateway projection.
Tasks
- identify all places where source-of-truth write and projection publish happen;
- add retry strategy for projection writes;
- make projection publication idempotent;
- define recovery behavior if projection publish fails after source-of-truth success;
- add dead-letter or repair strategy placeholder if needed later;
- document the consistency model.
Preferred Short-Term Outcome
- source-of-truth success is never reported as auth success unless projection write/publish reached the required success threshold, or the failure handling policy is explicit and tested.
Deliverables
- consistency policy document;
- tests for partial failure scenarios.
Exit Criteria
- known failure windows are explicit and bounded.
Stage 17. Add Public Anti-Abuse Hooks
Status: implemented.
Goal
Prepare the auth service for safe interaction behind gateway public routing.
Tasks
- add service-level hooks for challenge resend throttling;
- add max-attempt handling per challenge;
- add metrics for suppressed/blocked/sent flows;
- preserve soft anti-enumeration outward behavior.
Notes
Gateway already applies public-edge rate limits. This stage is about auth-specific flow protection, not replacing gateway limits.
Deliverables
- abuse-control policy inside auth domain;
- tests for throttling and attempt exhaustion.
Exit Criteria
- auth flow cannot be trivially abused through repeated confirm attempts.
Stage 18. Add Observability
Status: implemented.
Goal
Make the service operable from the beginning.
Tasks
- structured logs for all major state transitions;
- metrics for all major operations;
- tracing spans for public auth flow and internal API;
- redact secrets and codes from logs;
- include stable identifiers such as challenge id, device session id, user id, and reason codes where safe.
Minimum Metrics
- challenges created;
- deliveries sent/suppressed/failed;
- confirm attempts;
- confirm successes/failures;
- sessions created;
- session limit rejections;
- sessions revoked by reason;
- projection publish failures;
- user-resolution outcomes.
Deliverables
- metrics endpoint wiring if needed;
- logging/tracing middleware;
- observability tests where practical.
Exit Criteria
- production debugging is possible without adding ad hoc logs later.
Stage 19. Add Gateway-Compatibility Tests
Status: implemented.
Goal
Test auth-service not just in isolation, but against gateway expectations.
Tasks
- verify public auth HTTP DTO compatibility;
- verify
confirm-email-codereturns readydevice_session_id; - verify created session projection is readable by a gateway-compatible reader;
- verify revoked projection invalidates session;
- verify repeated confirm returns same session id in idempotency window;
- verify blocked e-mail still keeps
send_email_codeoutwardly success-shaped; - verify session limit exceeded returns stable client-visible error;
- verify malformed
client_public_keyis rejected.
Deliverables
- integration test suite focused on gateway contract.
Exit Criteria
- no ambiguity remains about integration with existing gateway behavior.
Stage 20. Add Real REST Adapter to User Service Contract
Status: implemented.
Goal
Prepare for future extraction of User Service.
Tasks
- define internal REST client for user resolution/create/block operations;
- keep stub implementation for tests;
- add timeout, retry, and error mapping policy;
- define normalized email rules at the boundary.
Deliverables
- REST client adapter for future user-service;
- compatibility tests using stub server.
Exit Criteria
- auth-service can later switch from stub to real user-service with no domain rewrite.
Stage 21. Add Real Mail Adapter Contract
Status: implemented.
Goal
Prepare for later internal mail-service-backed delivery.
Tasks
- define mail adapter request/response contract;
- preserve current stub for tests;
- define delivery timeout and error mapping;
- define how suppression vs explicit failure is represented.
Deliverables
- mail adapter interface finalized;
- optional HTTP client adapter skeleton.
Exit Criteria
- auth flow is decoupled from the future mail implementation.
Stage 22. Production Hardening Pass
Status: implemented.
Goal
Review edge cases before calling the service implementation complete.
Tasks
- test Redis reconnect behavior;
- test duplicate publish behavior;
- test crash/restart around confirm and revoke flows;
- test large numbers of active sessions per user;
- test concurrent confirms against the same challenge;
- test concurrent revoke and confirm races;
- test block-user during active auth flow;
- test expired challenge cleanup strategy.
Deliverables
- hardening checklist;
- race-condition tests;
- operational notes.
Exit Criteria
- no major known race remains undocumented.
Stage 23. Optional Cleanup and Migration Readiness
Status: implemented.
Goal
Make future SQL migration realistic.
Tasks
- review whether domain services leak Redis assumptions;
- ensure all store interfaces are storage-agnostic;
- isolate key naming, stream naming, and projection serialization;
- add adapter contract tests reusable by future SQL backends.
Deliverables
- backend-agnostic adapter tests;
- migration readiness notes.
Exit Criteria
- a future SQL backend can be added without reworking service-layer logic.
Recommended First Working Slice
If implementation needs an aggressively small first milestone, do this subset first:
- domain types
- service ports
- in-memory service logic
- stub
UserDirectory - stub
MailSender - public HTTP API
- Redis
SessionStore - Redis
ChallengeStore - Redis projection publisher
- gateway-compatibility tests for:
- send-email-code
- confirm-email-code
- session projection after confirm
This gives an end-to-end happy path quickly, without waiting for revoke/admin and full hardening.
Recommended Second Slice
- internal trusted API
- session-limit config provider
- revoke-device
- revoke-all
- block-user
- observability
- consistency safeguards
- hardening tests
Final Acceptance Criteria
The service can be considered implementation-ready when all of the following are true:
- gateway can call public auth routes synchronously;
confirm-email-codereturns a readydevice_session_id;- the created session appears in gateway-compatible projection storage;
- revoked sessions publish gateway-compatible revoke updates;
- repeated successful confirm returns the same session id during the short idempotency window;
- session creation respects dynamic limit config;
- user block prevents future auth flow and can revoke active sessions;
- all storage is hidden behind interfaces;
- auth-service is not required on the authenticated command hot path;
- logs, metrics, and tests cover the full lifecycle.
Implementation Order Summary
flowchart TD
A["Freeze contracts"]
B["Domain model"]
C["Ports"]
D["In-memory service logic"]
E["Redis stores"]
F["Projection publisher"]
G["Public HTTP API"]
H["Internal trusted API"]
I["Revoke and block flows"]
J["Observability and hardening"]
K["Gateway compatibility tests"]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J --> K