10 Quality Requirements

Quality Tree

Quality
├── Performance
│   ├── NFR-P1: Message latency ≤500ms under Silver load (500 concurrent/m5.large)
│   ├── NFR-P2: /sync response ≤1s under normal load
│   ├── NFR-P3: Silver-tier (>500 concurrent), Gold (>1000), Platinum (>5000) without Redis/NATS
│   └── NFR-P4: Gateway cold-start ≤5s
├── Security
│   ├── NFR-S1: All external connections TLS 1.2+ (1.3 preferred)
│   ├── NFR-S2: Sensitive PII encrypted at rest (X25519 key)
│   ├── NFR-S3: Audit log append-only + Ed25519-signed
│   ├── NFR-S4: OIDC token validation on every API request
│   ├── NFR-S5: Ed25519 key deletion is irreversible (GDPR compliance)
│   └── NFR-S6: Bootstrap mode disables permanently after first admin setup
├── Scalability
│   ├── NFR-SC1: Go Gateway horizontally scalable without session affinity
│   ├── NFR-SC2: Elixir/OTP Core supports cluster operation (Phase 2: libcluster)
│   └── NFR-SC3: No external middleware layer required (PostgreSQL only)
├── Reliability
│   ├── NFR-R1: OTP process isolation (Room crash → only that room affected)
│   ├── NFR-R2: No data loss on Gateway restart (PostgreSQL is source of truth)
│   └── NFR-R3: Rolling updates without full downtime (stateless Gateway)
├── Operability
│   ├── NFR-O1: Full deployment via docker compose up in ≤10 minutes
│   ├── NFR-O2: Health/readiness endpoints respond ≤200ms under load
│   ├── NFR-O3: Admin UI fully embedded in Gateway binary (no external deps)
│   └── NFR-O4: All Admin UI states reproducible via URL
├── Compliance
│   ├── NFR-C1: GDPR Right-to-be-forgotten via cryptographic key deletion
│   ├── NFR-C2: Audit log retention configurable (default: 7 years)
│   └── NFR-C3: All data in operator-controlled PostgreSQL (on-premise capable)
├── Matrix Protocol Conformance
│   ├── NFR-M1: Compatible with Element, FluffyChat, Hydrogen (incompatibilities = bugs)
│   ├── NFR-M2: OIDC integration via m.login.sso per Matrix OIDC Specification
│   ├── NFR-M3: Element Web browser-first E2E suite (playwright-bdd) validates login,
│   │          room create/join/leave, message send/receive, Space creation/sidebar
│   │          display, Space child room management, and restricted join rule enforcement
│   │          via real browser flows (15-9a: spaces_create.feature; 15-9b: spaces_restricted.feature)
│   ├── NFR-M4: POST /_matrix/client/v3/search returns only results from rooms the
│   │          requesting user is a member of (no cross-room leakage)
│   └── NFR-M5: Search results never include messages from encrypted rooms
│              (m.room.encryption state event present → room excluded from FTS results)
├── Accessibility
│   ├── NFR-A1: Admin UI WCAG 2.1 Level AA
│   ├── NFR-A2: Admin UI fully keyboard navigable
│   └── NFR-A3: Admin UI usable with screen readers (semantic HTML, ARIA)
└── Crypto Agility
    └── NFR-CR1: Cryptographic primitives modularized (replaceable without core refactor)

Performance Scenarios

ScenarioStimulusResponseMeasure
Silver-tier load500 concurrent users (60% sync, 20% send, 10% presence/typing, 5% room ops)≤500ms message latencyLoadtest on 2x AWS m5.large
Gateway restartSIGTERMHTTP traffic resumes≤5s cold start
Core restartdocker restart coreEvents resume delivery, no cold-syncRecovery via ETS + PostgreSQL checkpoint
gRPC stream lostNetwork partitionmessage_buffer absorbs writes; drain on reconnect0 message loss
Full-text searchPOST /search with keyword query across user's roomsGIN index hit, no seq-scan≤500ms at Silver load; scope enforced via room membership filter

Security Scenarios

ScenarioConcernResponse
Compromised tokenJWT stolenShort-lived OIDC token + deactivation endpoint invalidates all sessions
GDPR deletion requestUser wants data removedDelete private keys → all sensitive PII irrecoverable (audit log intact)
Compliance audit requestLegal request for message contentFour-eyes approval + 24h session limit + signed export
Search cross-room IDORCaller-supplied user_id bypasses membership filterNebu.Search.DB.search_messages/4 user_id parameter MUST come from validated session (gRPC metadata / JWT claim); Story 11.3 SearchMessages handler enforces this invariant; SQL-layer subquery on room_members WHERE left_at IS NULL prevents leakage even if called with a foreign user_id
Search on encrypted roomsCiphertext bodies returned in plaintext search responseNebu.Search.DB excludes rooms with m.room.encryption state event via NOT EXISTS subquery; no application-layer bypass possible (enforced in SQL, NFR-M5)

Source: _bmad-output/planning-artifacts/prd.md, §Non-Functional Requirements; _bmad-output/planning-artifacts/architecture.md, §Gaps & Open Questions; Story 11-2 (Nebu.Search.DB security invariants, encrypted-room exclusion)