Skip to content

Redis runtime policy ​

Status ​

Accepted for MVP documentation.

Date ​

2026-06-25

Context ​

MyHoreca Pay needs Redis for temporary runtime state that should not become permanent business history: temporary auth/session state, active user context, invitation tokens, Syrve/iiko runtime tokens, MFA/session state, throttling counters, and background job cache.

PostgreSQL remains the source of truth for permanent configuration, business records, audit history, users, organizations, roles, and financial workflow facts. Redis must not become a system of record for permanent data.

Invitations are the high-risk case in this policy: an invitation is intentionally a Redis-only runtime object with a short TTL. PostgreSQL stores audit events for the invite lifecycle, but it does not store the active invitation token or active invite object.

Decision ​

MyHoreca Pay adopts Redis Stack with NRedisStack as the target Redis runtime stack for MVP. Runtime JSON payloads are written through the typed Redis layer in Infrastructure and serialized only via PayJsonSerializer.

Decisions:

  • Redis is mandatory for temporary/runtime state in MVP.
  • Docker/dev Redis must enable persistence with AOF/RDB.
  • Invitation TTL is exactly 3 hours.
  • Invitation is a Redis-only runtime object.
  • A Redis-only invite may disappear after TTL expiry, Redis cleanup, Redis flush, or loss of Redis persistence.
  • Admins see active invites through Redis indexes.
  • Cancel invite deletes the Redis invite key and deletes or updates the Redis active indexes.
  • Invite audit events invite.created, invite.cancelled, and invite.accepted are written to PostgreSQL.
  • Permanent Syrve/iiko credentials must never be stored in Redis.
  • Redis may store Syrve/iiko runtime token/session state only when it is temporary and TTL-bound.
  • PostgreSQL remains the source of truth for audit, history, permanent configuration, and business records.
  • Redis is not the system of record for permanent data.

Redis responsibilities ​

Redis owns only temporary/runtime data:

  • temporary auth/session state;
  • active user, organization, and Outlet context;
  • invitation objects and invitation lookup indexes;
  • Syrve/iiko runtime tokens or session state with TTL;
  • MFA challenge/session state;
  • throttling counters;
  • background job cache and transient progress metadata.

Redis must not own:

  • users;
  • organizations, sub-organizations, or Outlets;
  • roles and role assignments;
  • permanent Syrve/iiko connection configuration;
  • permanent credentials, passwords, API keys, refresh secrets, or connection secrets;
  • audit history;
  • business records;
  • raw bank files, raw bank lines, full account numbers, or private banking payloads.

Redis vs PostgreSQL boundary ​

StoreResponsibilitiesNot allowed
RedisShort-lived runtime state, TTL-based tokens, indexes for active runtime objects, throttling counters, background job cache.Permanent configuration, audit/history, financial facts, permanent credentials, raw bank payloads, plaintext invitation tokens.
PostgreSQLUsers, organizations, roles, permanent Syrve connection configuration/credentials via approved secret storage, audit events, historical facts, business records.Raw runtime tokens, plaintext invitation tokens, transient MFA secrets, cache-only job payloads.

Boundary rules:

  • Redis data can be rebuilt, expire, or disappear without deleting PostgreSQL audit trail.
  • PostgreSQL audit can prove that an invite was created/cancelled/accepted even if the Redis invite no longer exists.
  • Redis indexes are runtime lookup aids, not audit evidence.
  • Permanent configuration may reference Redis-derived runtime behavior, but must not depend on Redis as the permanent record.

Runtime use-cases ​

Use-caseRedis rolePostgreSQL counterpart
Temporary auth/session stateTTL-bound login/session helper state.User, role, organization ownership, audit events.
Active contextCurrent user/org/Outlet context for a session.Organization, Outlet, user-role assignment records.
Invitation tokensRedis-only active invite object and lookup indexes, TTL 3 hours.invite.created, invite.cancelled, invite.accepted audit events.
Syrve/iiko runtime tokensShort-lived upstream token/session state, TTL not greater than upstream expiry.Permanent connection configuration and approved secret storage references.
MFA/session stateShort-lived challenge/session state.User and audit event records.
Throttling countersWindow-based counters for login, invite, posting, and API limits.Security/audit events only when policy requires recording.
Background job cacheTemporary progress/cache metadata, not the job source of truth.Durable job/outbox/business records where applicable.

Invitation policy ​

Invitation is a Redis-only runtime object with TTL 3 hours.

Rules:

  • Active invite object is stored only in Redis.
  • Active invite object must expire after 3 hours.
  • PostgreSQL stores audit events, not active invite payloads or plaintext tokens.
  • Redis invite value must not contain the plaintext invitation token when a token hash or derived lookup key can be used.
  • Redis key names must not contain raw secrets.
  • Admins list active invites using Redis indexes scoped by organization and recipient hash.
  • Cancel invite deletes the invite object key and deletes or updates every active invite index entry.
  • Accept invite validates the Redis object, performs the approved user/role workflow, deletes or marks the Redis invite inactive, and writes invite.accepted audit to PostgreSQL.
  • If Redis cleanup, flush, persistence loss, or TTL expiry removes an invite, the invite is no longer active. Admins can create a new invite; audit history remains in PostgreSQL.

Documentation schema for the Redis invite value:

json
{
  "inviteId": "uuid-v7",
  "storageOrganizationId": "uuid-v7",
  "organizationId": "uuid-v7|null",
  "locationId": "uuid-v7|null",
  "normalizedEmail": "lowercase-email-for-runtime-accept",
  "emailHash": "sha256-lowercase-email",
  "tokenHash": "sha256-invite-token",
  "roleCode": "OrganizationAdmin|Accountant|Viewer|...",
  "createdByUserId": "uuid-v7",
  "createdAtUtc": "ISO-8601",
  "expiresAtUtc": "ISO-8601",
  "correlationId": "string",
  "isActive": true,
  "expiresAtUnixSeconds": 1782483330
}

The runtime implementation uses a typed DTO and PayJsonSerializer. Plaintext invitation tokens must not be stored in the Redis value; use a token hash or derived lookup key. Recipient email may be stored only in normalized runtime form when it is required for the accept flow.

Syrve/iiko runtime token policy ​

Runtime Syrve/iiko token key pattern:

text
mhpay:{env}:org:{organizationId}:syrve:runtime-token:{connectionId}

Refresh lock key pattern:

text
mhpay:{env}:org:{organizationId}:syrve:runtime-token-refresh-lock:{connectionId}

Rules:

  • Redis may store only short-lived Syrve/iiko runtime token or session state.
  • TTL must be less than or equal to the actual upstream token/session expiry.
  • When upstream expiry is not available, TTL is bounded by Iiko.RestSessionVerification.SessionCacheSeconds; non-positive values disable Redis persistence for the acquired session.
  • Permanent Syrve/iiko credentials, API keys, passwords, refresh secrets, and connection secrets are forbidden in Redis.
  • Permanent credentials must remain in approved permanent secret storage or PostgreSQL-backed configuration according to existing architecture.
  • Runtime token payload must not be written to PostgreSQL audit.
  • Audit may record token lifecycle metadata such as actor/system actor, organization scope, connection id, action, timestamp, and correlation id, but not token values.
  • Runtime REST session acquisition first reads Redis when forceRefresh=false; forceRefresh=true bypasses the cached value and writes a fresh runtime state only after successful upstream auth.
  • Upstream login/refresh is guarded by a short Redis refresh lock. Concurrent callers wait briefly and re-read Redis so a single caller performs the upstream login under normal cache-miss/expired-token conditions.
  • REST-v3 runtime calls that use IIikoRestSessionClient.ExecuteWithSessionAsync invalidate the Redis runtime session on iiko_forbidden, reacquire under the refresh lock, and retry.
  • Reconnect retry stops after 7 forbidden/reconnect failures. The critical failure audit event contains only safe metadata: connection id, organization id, safe status code, attempt count, timestamp/correlation metadata. It must not contain runtime token/session id, passwords, password hashes, secret values, API keys, or raw request/response bodies.

Redis runtime session payload shape:

json
{
  "connectionId": "uuid",
  "organizationId": "uuid",
  "sessionId": "opaque-runtime-session-token",
  "createdAtUtc": "ISO-8601",
  "expiresAtUtc": "ISO-8601",
  "sessionTransport": "rest_session",
  "correlationId": "optional-safe-correlation"
}

The sessionId is a runtime-only upstream token/session value. It is permitted only in Redis with TTL and must never be copied into PostgreSQL audit/details, logs, UI/API responses, or test snapshots.

Persistence policy for docker/dev ​

Docker/dev Redis must enable Redis persistence:

  • AOF enabled for append-only recovery;
  • RDB enabled for snapshots where supported by the dev image/configuration;
  • persistence files must not be committed to the repository;
  • Redis passwords, API keys, or connection strings must not be committed.

Persistence does not make Redis a permanent source of truth. Persistence only reduces accidental loss of runtime state during local development and container restarts.

Security policy ​

Security rules:

  • Do not commit Redis passwords, API keys, connection strings, dumps, or persistence files.
  • Do not store permanent secrets in Redis.
  • Do not store plaintext invitation tokens in Redis values when a hash or derived lookup key can be used.
  • Do not store raw bank lines, full account numbers, Syrve/iiko passwords, Syrve/iiko API keys, refresh secrets, or permanent connection secrets in Redis runtime payloads.
  • Redis keys must not include raw secrets, raw invitation tokens, full account numbers, or private banking payloads.
  • Redis payloads must be tenant-scoped where tenant ownership exists.
  • Redis access must be treated as privileged infrastructure access.
  • RBAC and tenant checks must happen before creating, reading, cancelling, accepting, or indexing runtime objects.

Audit policy ​

PostgreSQL audit is required for invite lifecycle events:

  • invite.created;
  • invite.cancelled;
  • invite.accepted.

Invite audit events must include actor, organization scope, optional Outlet scope, invite id, action, timestamp, and correlation id. Audit must not include plaintext invitation token.

Redis cleanup/loss must not delete PostgreSQL audit trail. PostgreSQL audit can state that an invite was created even when the active Redis invite expired or disappeared.

For Syrve/iiko runtime tokens, audit may record operational metadata but must not persist token payloads.

Failure and cleanup behavior ​

Redis-only invites are intentionally fragile runtime objects:

  • TTL expiry makes the invite inactive.
  • Redis cleanup or flush removes active invite state.
  • Redis persistence failure can remove active invite state.
  • Deleted Redis invite state is not restored from PostgreSQL because PostgreSQL is audit/history, not active invite storage.
  • Missing invite state must fail closed: reject invite acceptance and require a new invite.

Cleanup rules:

  • Cancel invite deletes the invite object key.
  • Cancel invite deletes or updates active invite indexes by organization and recipient hash.
  • Invite acceptance deletes or marks inactive the Redis invite object and indexes.
  • Expired invite indexes must be removed by TTL-aware cleanup or opportunistic cleanup during list/read flows.
  • Background job cache cleanup must not delete durable job, outbox, audit, or business records.

Consequences ​

  • MVP has a clear temporary/runtime store boundary.
  • Active invitations can disappear without a PostgreSQL business delete; this is accepted by design.
  • Admin invite screens must query Redis indexes for active invites and PostgreSQL audit for history.
  • Redis Stack/NRedisStack wiring is present for typed runtime JSON and invite indexing without storing permanent secrets in Redis.
  • Future tests must cover TTL expiry, cancel cleanup, accepted invite cleanup, Redis loss behavior, and audit retention.
  • Operational runbooks must treat Redis persistence as useful but not authoritative.

Non-goals / Out of scope ​

  • Runtime backend changes.
  • Frontend changes.
  • EF migrations.
  • API contract changes.
  • Runtime code tests.
  • Real bank files, raw bank lines, full account numbers, secrets, passwords, API keys, Redis dumps, or private data.
  • New business decisions outside Redis runtime policy.
  • Making Redis a system of record for permanent data.