Cross-cutting requirements
Non-functional, security and privacy requirements in measurable form — performance budgets, availability and backups, scalability, compatibility, responsive back-office, i18n and RTL, accessibility, exports, logging and alerting, security controls, privacy and quality gates — each with its acceptance and its verification.
Cahier §9 (Exigences techniques et non fonctionnelles) and §10 (Sécurité, conformité et traçabilité) state the expectations in a few lines each. This page makes every one of them measurable: an item id, the requirement, the acceptance threshold and how and where it is verified. The parent requirements are NFR-01 to NFR-09 and SEC-01 to SEC-08; thresholds marked proposed are the team's proposal for HOCO to accept at the cadrage.
How to read this page
Item ids are <parent>.<n>: NFR-1.3 is the third measurable item of NFR-01. Items whose parent is a functional requirement follow the same scheme (ACL-5.2 refines ACL-05, ANA-5.1 refines ANA-05). SEC-0.n are baseline security controls that serve every SEC requirement (cahier §10 as a whole).
Verification methods
| Method | Meaning | Evidence |
|---|---|---|
| Test | Automated test in CI (unit, integration, end-to-end) | CI run linked to the release |
| Measure | Load, timing or size measurement under the reference conditions below | Report attached to the task (k6 output, timings table, Sentry query) |
| Inspect | Configuration or code review against a checklist | Checklist signed in the pull request or the task |
| Demo | Executed scenario during acceptance | Scenario evidence (acceptance scenarios) |
Reference conditions used by every timing below (Assumption, calibrated during the field test MOB-13):
| Condition | Value |
|---|---|
| "Normal mobile connection" (cahier §9) — profile 4G-DZ | 6 Mbit/s down, 1.5 Mbit/s up, 120 ms round trip, 1 % packet loss |
| Mid-range Android reference device | 4 GB RAM, Helio G85/G99 or Snapdragon 680 class, Android 13–14 (for example Samsung Galaxy A15, Xiaomi Redmi 13C) |
| Office connection for the back-office | 10 Mbit/s, 40 ms round trip, recent desktop browser |
| Data volume | The generalisation column of the sizing table multiplied by 3 |
| Server | The production sizing of deployment and operations |
Performance — NFR-01
Cahier §9 « Temps d'ouverture cible inférieur à 3 secondes sur une connexion mobile normale »; §5.1 « La recherche et les filtres retournent les résultats corrects en moins de 3 secondes »; §5.3 « apparaît immédiatement dans le back-office ».
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-1.1 | API read latency | p95 under 300 ms server-side (request received → response sent) at 50 concurrent users for 15 minutes; mix 60 % catalog reads (list, facets, detail), 15 % cart, 10 % orders and finance reads, 10 % delegate reads, 5 % back-office lists (proposed) | Measure — k6 in SRV-28 |
| NFR-1.2 | API write latency | p95 under 800 ms for cart.checkout, admin.orders.validate, delegate.visits.arrive and other mutations, same load (proposed) | Measure — k6 in SRV-28 |
| NFR-1.3 | Catalog search and filters end to end | From the tap on search or on a filter to the first 20 results rendered (images may still load): p90 ≤ 3 s on 4G-DZ and the reference device, with up to three combined filters (CAT-07, CAT-12) | Measure — timed runs in MOB-12; Sentry transaction catalog.search p90 during the pilot |
| NFR-1.4 | Facet counts | catalog.facets p95 under 400 ms server-side on the full catalog with three filters combined; counts equal the number of results returned by catalog.products.list for the same filters | Measure (k6) and Test (count consistency) in SRV-28 and SRV-11 |
| NFR-1.5 | Mobile cold start | Release build on the reference device: first frame ≤ 1.5 s, home usable with cached data ≤ 3 s, home refreshed from the network ≤ 3 s on 4G-DZ (median of 5 runs; no run above 4 s) | Measure — adb shell am start -W and Sentry app-start in MOB-12; regression check in MOB-15 |
| NFR-1.6 | Back-office list pages | Largest contentful paint ≤ 2 s for a 25-row table with filters applied; interaction to next paint ≤ 200 ms (proposed) | Measure — Playwright with Web Vitals on the main lists in SRV-28 |
| NFR-1.7 | Checkout end to end | Tap on « Valider la commande » → confirmation screen p90 ≤ 2 s on 4G-DZ | Measure — MOB-12 |
| NFR-1.8 | Live updates | Commit of a status change → reseller screen refreshed through events.stream p95 ≤ 5 s; new order → visible in the back-office queue through admin.events.stream p95 ≤ 5 s (ORD-15, SYN-07) | Demo — AT-30, AT-43 with server and client timestamps |
| NFR-1.9 | Offline replay | sync.batch of 50 queued mutations (photos excluded) p95 under 3 s server-side | Measure — SRV-28 |
| NFR-1.10 | Payload and asset budgets | Cursor list page of 20 items ≤ 60 KB gzip; thumbnails WebP ≤ 30 KB at 320 px; detail images ≤ 200 KB at 1 280 px; download size ≤ 35 MB per app on arm64 | Measure — response-size test in CI; image pipeline test; Play Console size report in MOB-14 |
| NFR-1.11 | Documents | Invoice or delivery-note PDF generated p95 under 5 s; larger documents generated asynchronously with a notification | Measure — SRV-28 |
Exports have their own budget (ANA-5.3).
Availability, backups and restore — NFR-02, SEC-05
Cahier §9 « Service supervisé avec sauvegardes et procédure de restauration »; §10 « Sauvegardes automatiques, politique de rétention et tests de restauration ».
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-2.1 | Monthly availability | ≥ 99.5 % for the API, the back-office and the reseller web (about 3 h 40 min of unplanned downtime per month), announced maintenance excluded (proposed) | Measure — external uptime monitor, monthly support report |
| NFR-2.2 | Planned maintenance | Window Friday 06:00–09:00 Africa/Algiers, announced 48 h ahead (announcement + banner); during it the API answers 503 MAINTENANCE with details.until and the apps show their maintenance state (RA-36, DA-28) | Demo — maintenance mode switched on in pre-production |
| NFR-2.3 | Field work survives an outage | A delegate completes a full tour with no server (offline-first, SYN-04); everything syncs afterwards without loss or duplicate | Demo — AT-15 |
| NFR-2.4 | Recovery point objective | ≤ 15 minutes with continuous WAL archiving (point-in-time recovery); ≤ 24 hours if the hosting option offers only nightly dumps (proposed, D-15) | Inspect — backup configuration in OPS-02 |
| NFR-2.5 | Recovery time objective | ≤ 4 hours from the decision to restore to the service open again, following the restore runbook (proposed) | Measure — timed restore drill in SRV-28 and every quarter |
| SEC-5.1 | Automatic backups | Nightly logical dump at 02:00 plus WAL archiving; private object storage versioned and copied nightly; all copies off the production server, on another provider or account | Inspect — OPS-02; alert if no successful backup by 04:00 |
| SEC-5.2 | Retention | 7 daily, 4 weekly and 12 monthly copies; deleted personal data therefore persists in backups up to 12 months, which the privacy policy states | Inspect — lifecycle rules of the backup bucket |
| SEC-5.3 | Encryption | Dumps encrypted before upload (public-key encryption; the private key held offline by Anis and a person designated by HOCO); bucket encryption at rest enabled | Inspect — OPS-02 |
| SEC-5.4 | Restore tests | Monthly restore of the latest dump into a scratch database with integrity checks (row counts, last business numbers, migration status); logged with duration and result | Measure — restore log; first run in SRV-28 |
Scalability — NFR-03
Cahier §9 « Architecture capable d'augmenter le nombre de revendeurs, produits, commandes et zones ». The cahier gives no volumes; the table below is the team's assumption, to be confirmed by HOCO at the cadrage, and drives the sizing and the load tests.
| Dimension | Pilot | Generalisation (12 months) | Design headroom |
|---|---|---|---|
| Active resellers | 40 | 3 000 | 10 000 |
| CRM records (prospects included) | 200 | 8 000 | 30 000 |
| Delegates · staff users | 3 · 10 | 30 · 25 | 100 · 60 |
| Products · variants | 1 500 · 2 500 | 2 000 · 3 500 | 10 000 · 20 000 |
| Orders per day (average · peak) | 10 · 30 | 300 · 1 000 | 3 000 · 10 000 |
| Lines per order (average) | 12 | 12 | 12 |
| Visits per day | 40 | 450 | 1 500 |
| Location pings per day | 1 000 | 10 000 | 40 000 |
| Photos per day | 80 | 900 | 3 000 |
| Concurrent reseller SSE connections (peak) | 30 | 600 | 2 500 |
Derived growth at generalisation: database under 20 GB per year (orders and lines about 1 GB, audit and notifications the largest tables); object storage about 70–100 GB per year, mostly visit and store photos — the reason visit-photo retention is in D-11.
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-3.1 | Headroom without code change | NFR-1.1 and NFR-1.2 met with the generalisation data ×3 on the production sizing; the design headroom reached by vertical scaling and API replicas only | Measure — SRV-28 with a seeded volume |
| NFR-3.2 | Stateless API | No session, cache or SSE subscription state in process memory that another replica needs: sessions in PostgreSQL, rate limits and SSE fan-out through Redis pub/sub; two api replicas pass the end-to-end suite | Test — CI job with two replicas behind a proxy |
| NFR-3.3 | Safe background jobs | Every job idempotent; scheduled jobs run once even with several worker processes (repeatable-job keys); a job retried after a crash produces the same result | Test — job tests in SRV-08 |
| NFR-3.4 | Large tables | Keyset pagination on every list; LocationPing, AuditLog, Notification and StockMovement indexed on their access paths and purged or archived by retention jobs; no query in the p95 path scans a full table | Inspect — slow-query and index pass (pg_stat_statements) in SRV-28 |
| NFR-3.5 | Search engine | PostgreSQL full-text search with unaccent and trigram indexes up to the design headroom; a dedicated search engine is not part of V1 (Assumption) | Measure — NFR-1.3 and NFR-1.4 at headroom volume |
Compatibility — NFR-04
Cahier §9 « Android prioritaire ; iOS selon décision du client ; téléphones milieu de gamme ».
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-4.1 | Android versions | Reseller app minSdk 24 (Android 7.0); delegate app minSdk 26 (Android 8.0, notification channels and foreground-service behaviour); targetSdk = the level Google Play requires at submission time (proposed; Imad confirms in MOB-01, the mobile guide is authoritative) | Inspect — Gradle configuration |
| NFR-4.2 | Device coverage | Every release passes the smoke suite on the tier-1 devices; every store release on tier 1 and tier 2 (table below) | Demo — device matrix in MOB-12, MOB-15 |
| NFR-4.3 | iOS | Only if D-10 says so: iOS 15 or later, same feature set | Inspect — MOB-16 |
| NFR-4.4 | Browsers | Back-office and reseller web: the last two versions of Chrome, Edge and Firefox, Safari 16.4 or later; reseller web also on Chrome for Android and Samsung Internet (last two versions) | Test — Playwright on Chromium, Firefox and WebKit |
| NFR-4.5 | Screen sizes | Apps from 360 × 640 dp to 430 dp wide in portrait, font scale 0.85–1.3; reseller web from 360 px to 1 920 px; back-office per NFR-05 | Demo — design QA DQA-02, DQA-03, DQA-05 |
| Tier | Devices (proposed — the pilot users' phones are surveyed at the cadrage) | Why |
|---|---|---|
| 1 | Samsung Galaxy A15 or A16 (Android 14–15) · Xiaomi Redmi Note 13 or Redmi 13C (HyperOS) · the model most used by HOCO's delegates | Reference mid-range; aggressive battery management on Xiaomi |
| 2 | An Oppo or Realme A-series phone (ColorOS) · a 2–3 GB RAM phone on Android 10–11 · an Android 7 or 8 emulator for the minimum SDK | Battery savers, low memory, oldest supported system |
| 3 (if iOS) | iPhone SE (2nd generation or later) and a recent iPhone | Smallest and current screens |
Responsive back-office — NFR-05
Cahier §9 « Back-office utilisable sur ordinateur, tablette et grand écran ».
| Width | Layout |
|---|---|
| ≥ 1 920 px | Dashboards spread to more cards per row; tables show optional columns; content width capped for forms |
| 1 280–1 919 px | Reference layout: expanded sidebar, full tables |
| 1 024–1 279 px | Sidebar collapsed to icons; secondary columns hidden behind a column picker |
| 768–1 023 px (tablet) | Sidebar as a drawer; tables scroll horizontally with a sticky first column; forms in one column; drawers become full-screen sheets |
| Below 768 px | Consultation and quick actions only (orders to confirm, validation queue, order detail); not optimised for data entry |
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-5.1 | Every back-office page usable from 1 024 px | No horizontal page scroll at 1 024 px (tables scroll inside their container); every action reachable | Demo — DQA-04 at 1 024, 1 280 and 1 920 px |
| NFR-5.2 | Tablet use | On a 10-inch tablet in landscape, the orders-to-confirm queue, order detail, delivery board and validation queue are fully operable by touch; targets ≥ 40 px | Demo — DQA-04 on a real tablet |
| NFR-5.3 | Large screens | At 2 560 px the dashboards fill the width without stretched charts; text line length stays readable | Demo — DQA-04 |
Languages, internationalisation and RTL — NFR-06
Cahier §9 « Français en V1 ; arabe et anglais prévus selon validation ». French is the only active language at launch unless D-09 decides otherwise; everything is built so that Arabic and English are a translation and QA job, not a refactor.
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-6.1 | No hard-coded text | Every user-facing string comes from message files: ARB (app_fr.arb, app_ar.arb, app_en.arb) in Flutter, next-intl JSON in the Next apps; server errors and templates per locale | Test — pseudo-locale build (accented, 30 % longer strings) shows no untranslated text on the main screens; CI fails on keys missing from ar or en files (empty values allowed, they fall back to fr) |
| NFR-6.2 | ICU messages | Plurals, gender and variables use ICU MessageFormat; no string concatenation of sentence fragments | Inspect — code review checklist |
| NFR-6.3 | Locale negotiation | User locale from the profile (me.update); API messages follow Accept-Language (fr default, ar, en); enum values are never sent as labels — clients translate them | Test — API returns Arabic messages with Accept-Language: ar |
| NFR-6.4 | Logical layout | Web: CSS logical properties only (margin-inline-start, padding-inline-end, inset-inline-start, text-align: start; Tailwind ms-*, me-*, ps-*, pe-*, start-*, end-*); physical ml-*, mr-*, left-*, right-* forbidden for layout. Flutter: EdgeInsetsDirectional, AlignmentDirectional, PositionedDirectional | Test — CI grep rule on the web apps; Flutter lint rule in MOB-07 |
| NFR-6.5 | Direction | ar sets dir="rtl" and lang="ar" on the web and TextDirection.rtl in Flutter; directional icons (back, chevrons, progress arrows) mirror; non-directional icons (play, check, clocks, brand logos) do not | Demo — AT-31 |
| NFR-6.6 | Arabic typography | An Arabic typeface paired with the Latin one in the design system (DES-02), self-hosted; Arabic line height about 1.5; no letter-spacing and no uppercase transform on Arabic text | Inspect — design tokens; Demo AT-31 |
| NFR-6.7 | Numbers, money and dates | fr-DZ formats: 125 000,00 DA, 03/11/2026, 24-hour time; Arabic uses the ar-DZ locale with Latin digits; the currency label in Arabic is part of D-09; Gregorian calendar; all times shown in Africa/Algiers | Test — formatter unit tests in MOB-07 and the web apps |
| NFR-6.8 | Mixed-direction strings | Business numbers, phone numbers, amounts and product references inside Arabic text are isolated as left-to-right (<bdi> or dir="ltr" on the web, an LTR TextSpan in Flutter) | Demo — AT-31 |
| NFR-6.9 | Reference data | Wilaya and commune names in French and Arabic (GEO-01); translatable content (categories, attributes, promotions, announcements) stored so that an Arabic value can be added without a schema change | Inspect — database design review |
| NFR-6.10 | Text expansion | Layouts tolerate 30 % longer labels without truncating actions or amounts; buttons have no fixed width | Demo — pseudo-locale screenshots in DQA-02 to DQA-05 |
Accessibility — NFR-07
Cahier §9 « Contrastes lisibles, boutons accessibles, tailles de texte cohérentes et messages explicites ». Target: WCAG 2.1 level AA for what applies to each interface (proposed).
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-7.1 | Contrast | Text ≥ 4.5:1, large text and UI components (borders of inputs, status pills, icons that carry meaning) ≥ 3:1, in every status tone of the design system | Test — Flutter textContrastGuideline; axe-core on the web pages; token check in DES-02 |
| NFR-7.2 | Touch targets | ≥ 48 × 48 dp in the apps, ≥ 40 × 40 px for back-office controls, ≥ 24 × 24 px minimum spacing for dense tables | Test — Flutter androidTapTargetGuideline; Inspect — design QA |
| NFR-7.3 | Text scaling | Apps usable at system font scale 1.3 without clipped amounts, buttons or status; web usable at 200 % browser zoom | Demo — MOB-17, DQA-04 |
| NFR-7.4 | Screen readers | Every icon-only button has a label (Flutter Semantics, web aria-label); status pills expose their text; images have alternatives or are decorative | Test — Flutter labeledTapTargetGuideline; axe-core: zero serious or critical violations on the main pages |
| NFR-7.5 | Not by colour alone | Status, stock availability and validation errors always combine colour with text or an icon | Inspect — design QA |
| NFR-7.6 | Keyboard | Back-office fully operable with the keyboard: visible focus, logical tab order, dialogs trap focus and close with Escape, tables navigable | Test — Playwright keyboard paths on the orders-to-confirm queue and the order detail |
| NFR-7.7 | Explicit messages | Every error says what happened and what to do next, in French, from the error code's localised message; no raw codes or stack traces shown to users | Inspect — copy review in DQA-10 |
| NFR-7.8 | Outdoor readability | Delegate app readable in direct sunlight at full brightness: key screens use the high-contrast tones of the design system | Demo — DQA-03 outdoor check |
Exports — ANA-05
Cahier §9 « PDF et Excel/CSV pour les listes et rapports autorisés »; ANA-05.
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| ANA-5.1 | Formats | Lists and reports export to XLSX and CSV; reports and documents to PDF (ExportFormat); XLSX amounts are numbers formatted in DA with two decimals, dates real dates; CSV is UTF-8 with BOM and ; separator so that French Excel opens it correctly | Test — export snapshot tests in SRV-25 |
| ANA-5.2 | Permission | Exporting needs <module>.export (for example crm.export, reports.export); the export contains only rows and columns the requester may see (purchase prices only with the pricing permission, ACL-04) | Test — permission tests; Demo AT-27 |
| ANA-5.3 | Size and duration | Asynchronous ExportJob; up to 100 000 rows, streamed (bounded memory); 10 000 rows in under 30 s, a 50-page PDF in under 60 s | Measure — SRV-28 |
| ANA-5.4 | Protected files | Stored as GENERATED_DOCUMENT in the private bucket, downloaded through a 5-minute signed URL issued to the requester only, deleted after exports.retentionDays (default 7) | Test — SRV-25 |
| ANA-5.5 | Traceability | AuditLog EXPORT with the filters used and the row count | Test — SRV-25 |
Logging, monitoring and alerting — NFR-08
Cahier §9 « Journalisation des actions sensibles et erreurs techniques »; §10 « Journal d'activité : connexions, créations, modifications, validations, exports et suppressions ». Two separate records exist: the activity journal (AuditLog, business-facing, kept per the retention table, AUD-01) and technical logs (operations-facing, 30 days).
What technical logs contain
| Source | Fields |
|---|---|
| API request log (pino, JSON) | requestId, timestamp, operation id, method, route template (not the full URL with query), status, duration, userId (UUID), userType, ClientApp, app version, error code |
| Worker job log | Queue, job id, attempt, duration, outcome, error code |
| Security events | Sign-in success and failure (user id or masked phone), lockouts, permission denials, rate-limit hits, 426 version refusals |
| Integration calls | Provider, operation, status, latency, provider message id (never the content of SMS or emails) |
What is never logged — enforced by pino redact paths, Sentry beforeSend scrubbing with sendDefaultPii off, and a test that sends a request containing each field and asserts it is absent from the log output:
| Never in logs | Why |
|---|---|
| Passwords, temporary passwords, OTP codes, verification tokens | Credentials |
Session tokens, Authorization and Cookie headers, FCM tokens, signed URLs | Replayable secrets |
| Full phone numbers and emails | Personal data — masked as +213 55* ** ** 56 |
Coordinates (check-in positions, location pings, the location sent to support.whatsapp) | Location data lives only in the database under its retention rule — never in logs, traces or error reports |
| Request and response bodies of auth, finance and files operations | Personal and financial data |
| Content of payment proofs, documents and photos | Personal data |
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| NFR-8.1 | Correlation | Every request has a requestId returned in the error envelope and the X-Request-Id header, propagated to jobs it enqueues and to Sentry | Test — SRV-04 |
| NFR-8.2 | Redaction | The redaction test above passes; no field of the "never" list appears in a log, trace or error report | Test — CI |
| NFR-8.3 | Error tracking | Sentry in the server, both Next apps and both Flutter apps, with release tags, source maps and Dart symbols uploaded by CI; crash-free sessions ≥ 99.5 % during the pilot | Measure — Sentry release health |
| NFR-8.4 | Metrics | Request rate, latency percentiles and error rate per operation; queue depth, oldest waiting job and failed jobs per queue; database connections and slow queries; disk; SSE connections; SMS delivery rate and push failure rate | Inspect — dashboards in OPS-02 |
| NFR-8.5 | Alerting | Every alert of the alert table reaches the on-call person within 5 minutes; a test alert is fired before go-live | Demo — go-live checklist |
| NFR-8.6 | Log retention | Technical logs 30 days; Sentry events 90 days; AuditLog per the retention table | Inspect — OPS-02 |
| NFR-8.7 | Activity journal completeness | Sign-ins, failures, creations, modifications, status changes, validations, rejections, overrides, exports, imports, permission changes and deletions are journaled with actor, time, app, IP and before/after values | Test — journal assertions in each module's integration tests; Demo AT-14 |
Security controls
Authentication and sessions — SEC-01, SEC-02
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-1.1 | Password storage | Memory-hard hashing (Better Auth's default scrypt, or Argon2id) with per-user salt; no reversible storage; parameters reviewed in SRV-28 | Inspect — SRV-05 |
| SEC-1.2 | Password policy | 8 to 128 characters for resellers and delegates, 10 minimum for staff; any character allowed; refused if among the most common passwords or equal to the phone number (AUTH-07) | Test — SRV-05 |
| SEC-1.3 | One-time codes | 6 digits, valid 5 minutes, 5 attempts, resend after 60 s, 5 per hour per phone and 20 per hour per IP; stored hashed; invalidated after use | Test — SRV-05; Demo AT-33 |
| SEC-1.4 | Temporary passwords | Created by admin.users.create or reset: valid 72 hours, single use, change forced at first sign-in | Test — SRV-05 |
| SEC-2.1 | Session lifetime | Resellers and delegates 30 days sliding; back-office 7 days sliding with re-authentication for sensitive actions (D-33) | Test — SRV-05 |
| SEC-2.2 | Revocation | Sign-out revokes the current session and unregisters its push token; a password change revokes the other sessions; suspension or deactivation revokes all sessions at once; a user can list and revoke their sessions (AUTH-06) | Test — SRV-05; Demo AT-32 |
| SEC-2.3 | Attempt limiting | Sign-in 5 attempts per minute per phone and IP, lockout 15 minutes after 5 consecutive failures; generic error message that does not reveal whether the phone exists | Test — SRV-05 |
| SEC-2.4 | Token handling on clients | Mobile tokens in secure storage (Android Keystore through flutter_secure_storage), never in logs or plain preferences; web sessions in HttpOnly, Secure, SameSite=Lax cookies with an environment-specific name so staging and production sessions never mix | Inspect — MOB-02, DSH-01 |
Access control on every operation — ACL-05
Cahier §10 « Contrôle d'accès par rôle et permission sur chaque API »; §3.1.
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| ACL-5.1 | Declared permission on every route | Every back-office operation declares its permission key in the contract; every reseller operation the self scope; every delegate operation the delegate scope; public and optional operations are listed explicitly | Test — a sweep test enumerates all contract routes and fails if one has no declared rule (SRV-06, SRV-28) |
| ACL-5.2 | Scope as query filter | Scopes are applied in the data-access layer, not in handlers; a record out of scope answers 404 NOT_FOUND so its existence does not leak | Test — per-module scope tests; Demo AT-22, AT-23 |
| ACL-5.3 | Field-level restrictions | Purchase prices, margins and credit data only for holders of the matching permission (ACL-04); delegate order views never include purchase prices | Test — response-schema tests per role |
| ACL-5.4 | Quantities never leave the back-office | Reseller, guest and delegate response schemas contain no stock quantity field — only availability IN_STOCK or OUT_OF_STOCK (STK-07); the contract test fails if such a field appears | Test — contract snapshot; Demo AT-41 |
| ACL-5.5 | Guest access | optional-auth catalog operations return no price (price: null) and no customer-type information without a session; cart and order operations refuse guests | Test — catalog tests in SRV-11; Demo AT-38 |
| ACL-5.6 | UI mirrors the server | The apps hide what the user cannot do, using me.get permissions and allowedActions; hiding is never the protection | Demo — AT-14 |
Input validation and API hardening
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-0.1 | Strict input schemas | Every input validated by Zod 4 strict objects: unknown fields rejected (400 VALIDATION_FAILED), string lengths bounded (notes ≤ 300 characters, messages ≤ 2 000), quantities within allowed ranges, enums exact, phones normalised to E.164, ids UUIDv7, dates ISO-8601 | Test — contract tests in SRV-03 |
| SEC-0.2 | Output schemas | Responses typed by the contract and stripped of fields outside it, so a new database column cannot leak | Test — contract snapshot |
| SEC-0.3 | Injection | Database access through Prisma; raw SQL only with tagged templates — $queryRawUnsafe with user input is forbidden (CI grep rule); no HTML rendering of user input without escaping (PDF templates included) | Test — CI rule; Inspect |
| SEC-0.4 | Browser protections | Strict Content-Security-Policy with nonces on the Next apps, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy (no camera, microphone or geolocation on the back-office), frame-ancestors 'none'; CORS allow-list of the exact app origins with credentials; state-changing cookie requests must come from an allowed Origin | Inspect — headers check in SRV-28 |
| SEC-0.5 | Abuse limits | General limit 600 requests per minute per user; anonymous catalog and invitation calls limited per IP (30 per minute for invitations; proposed 120 per minute for guest catalog calls) to make bulk scraping of the guest catalog slow; 429 RATE_LIMITED with Retry-After | Test — SRV-04 |
| SEC-0.6 | Version enforcement | Below the minimum version the API answers 426 UPGRADE_REQUIRED (AUTH-09) so a vulnerable app version can be retired | Demo — AT-28 |
Files and documents — SEC-04
Cahier §10 « Protection des documents et fichiers téléchargés ».
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-4.1 | Private by default | Only product images, brand logos and banners are public (CDN); every other FilePurpose lives in the private bucket, which denies public access | Inspect — bucket policies in OPS-02 |
| SEC-4.2 | Upload checks | The pre-signed PUT (10 minutes) is bound to the declared MIME type; files.completeUpload checks the stored object's real size against the purpose's limit (5 MB for photos, 10 MB for documents, proofs and attachments) and its magic bytes against the allowed types, and deletes it on mismatch | Test — SRV-07 |
| SEC-4.3 | Image sanitising | Images are re-encoded (thumbnails and display sizes), which strips EXIF metadata including GPS; originals of photos are not served | Test — SRV-07 |
| SEC-4.4 | Signed downloads | Private files are read through 5-minute signed GET URLs issued after an access check of the caller on the owning record; URLs are never logged | Test — SRV-07; Demo AT-22 |
| SEC-4.5 | Malware scanning | Documents, payment proofs and ticket attachments scanned before staff can open them (recommended; a scanner container costs about 1–1.5 GB of RAM — decided with the sizing in D-15) | Inspect — OPS-02 |
Transport — SEC-03
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-3.1 | HTTPS everywhere | TLS 1.2 or later (1.3 preferred) on every public endpoint; HTTP redirects to HTTPS; certificates renewed automatically with an alert 14 days before expiry | Inspect — TLS scan in OPS-02 |
| SEC-3.2 | HSTS | Strict-Transport-Security one year, includeSubDomains once every subdomain is HTTPS | Inspect — headers check |
| SEC-3.3 | No clear-text mobile traffic | Android network security configuration forbids clear text in staging and production flavors; no certificate pinning in V1 (automatic certificate rotation would break pinned apps) | Inspect — MOB-01 |
Secrets, dependencies and infrastructure
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-0.7 | Secrets management | Secrets only in the environment of each deployment (Dokploy) and in GitHub Actions secrets, validated at boot by the env package; one set per environment; never in the repositories; rotation runbook (deployment and operations) applied on any suspected leak and on every team departure | Inspect — OPS-02 |
| SEC-0.8 | Secret scanning | gitleaks in the pre-commit hook and as a CI job on every push of both repositories; a finding fails the build | Test — CI |
| SEC-0.9 | Dependency scanning | Dependabot (npm, pub, GitHub Actions, Docker); pnpm audit --prod fails CI on high or critical issues with a fix available; container images scanned before deploy; monthly dependency update round | Test — CI; Inspect — monthly maintenance report |
| SEC-0.10 | Least privilege on infrastructure | SSH by key only, no root login, firewall open on 80 and 443 only (SSH restricted); database and Redis reachable only on the internal network; separate database roles for the application (data only), migrations (schema) and backups; storage credentials per bucket and per environment; Firebase service account limited to sending messages; Dokploy and provider consoles behind two-factor authentication | Inspect — infrastructure checklist in OPS-02 and SRV-28 |
| SEC-0.11 | Patching | Operating-system security updates applied automatically; PostgreSQL and Redis minor updates monthly; critical vulnerabilities patched within 7 days (proposed) | Inspect — monthly maintenance report |
Account deactivation without losing history — SEC-06
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-6.1 | Deactivate, never delete | Users go SUSPENDED or DISABLED (admin.users.setStatus); their orders, visits, tours, payments and journal entries stay intact and attributed; foreign keys never cascade deletes onto history | Test — SRV-05; Demo AT-32 |
| SEC-6.2 | Delete means archive | Deleting a referenced business record (brand, category, reference item) archives it | Test — module tests |
| SEC-6.3 | Erasure by anonymisation | A personal-data erasure replaces names, phones, emails and photos by placeholders while keeping legally required records (privacy) | Test — anonymisation script test |
Privacy and compliance — SEC-07, SEC-08
Cahier §10 « Consentement et transparence concernant la géolocalisation des délégués » and « Politique de confidentialité, propriété et durée de conservation des données à valider avec le client ». The applicable law is Law 18-07 of 10 June 2018 on the protection of natural persons in the processing of personal data, supervised by the national authority (ANPDP). HOCO is the data controller; the development team acts as a processor during build and maintenance. Every legal point below is to validate with HOCO's counsel.
Delegate geolocation — SEC-07
| Id | Requirement | Acceptance | Verified by |
|---|---|---|---|
| SEC-7.1 | Informed consent before the first tour | DA-04 explains in plain French what is collected (positions during tours only), how often (every 5 minutes or 200 m, D-07), who sees it (users with field.view), how long it is kept (90 days) and how to stop it (suspend or close the tour); the consent is recorded with the text version, date and app version, and asked again when the text changes | Test — MOB-10; Demo AT-09 |
| SEC-7.2 | No tracking outside tours | Sampling runs only while a tour is IN_PROGRESS (foreground service with a persistent notification, D-24); pings received for a tour in any other status are discarded and counted | Test — SRV-21 and MDA-12; Demo AT-09 |
| SEC-7.3 | Transparency | The delegate can see in DA-26 whether location is active, the consent given and the retention | Demo — AT-09 |
| SEC-7.4 | Proportionate access | Trails visible only to field.view holders, per tour; exports of trails are not offered in V1 | Test — permission tests |
| SEC-7.5 | Labour-law fit | The use of positions for performance review is described in the delegates' working rules (to validate with HOCO's counsel) | Inspect — HOCO's sign-off |
Data minimisation
- Only the data sets of cahier §15.4 and §15.5 are collected; identity documents (
ID_CARD) are optional and visible only to users withcrm.validateand finance roles (proposed). - Device location for the WhatsApp support region (SUP-05) is used on the device and sent to
support.whatsapponly to resolve the nearest commune, wilaya and region; it is never stored, logged or attached to the guest or the account, and it is requested only when the user taps the button without a declared wilaya (Android permission while using the app; refusing leads to the wilaya picker). - No access to contacts, call logs or SMS inboxes; the OTP is read through the SMS User Consent or Retriever APIs, which need no SMS permission.
- No advertising identifiers, no third-party analytics or trackers in the apps; Firebase is used for messaging only; crash reports carry no personal data (NFR-8.2).
- Photos are stripped of metadata (SEC-4.3); store photos show the shop, not people, as instructed in the delegate field guide.
Retention
Proposed durations — to validate with HOCO's counsel and accountant (D-11). Each duration is a setting applied by a purge or anonymisation job.
| Data category | Proposed retention | Then | Basis (to validate) |
|---|---|---|---|
| Reseller account and CRM record | While the commercial relationship lasts | Anonymised 3 years after the last order or interaction | Contract; legitimate interest |
Prospect never converted (NOT_INTERESTED or no activity) | 3 years after the last interaction | Anonymised | Legitimate interest |
| Orders, invoices, credit notes, payments, delivery notes, payment proofs | 10 years | Deleted | Commercial and tax record keeping — to confirm with HOCO's accountant |
| Returns and after-sales claims with photos | 3 years after closure; photos 2 years | Deleted | Warranty disputes |
Location pings (LocationPing) | 90 days | Deleted (daily kilometre totals kept with the tour) | Consent and legitimate interest (D-07) |
| Visit records (check-in position, checklist, report) | 3 years | Anonymised delegate link kept for statistics | Legitimate interest |
| Visit photos · store photos | 2 years · while the reseller is active | Deleted | Legitimate interest |
| Store documents (RC, NIF, NIS, AI, ID) | While the reseller is active + 1 year | Deleted | Contract |
| Support tickets and messages | 3 years after closure | Deleted | Legitimate interest |
| Notifications (inbox) · delivery records | 12 months · 90 days | Deleted | Service |
| One-time codes · SMS send log | Until expiry + 24 hours · 90 days | Deleted | Security |
| Sessions · device push tokens | Until expiry or revocation + 30 days · until sign-out or 60 days without activity | Deleted | Security, service |
Activity journal (AuditLog) | 5 years | Deleted | Security and accountability |
| Technical logs · error reports | 30 days · 90 days | Deleted | Security |
| Idempotency records | 30 days | Deleted | Service |
| Export files · import files | 7 days · 90 days | Deleted | Service |
| Invitations and funnel events | 12 months after the last step | Aggregated | Legitimate interest |
| Backups | 12 months (rolling) | Overwritten | Security |
Data subject requests
| Request | How it is made | How it is fulfilled | Target |
|---|---|---|---|
| Information | Privacy policy linked from sign-up, settings and the store listings | Published before the Play closed track (MOB-14) | Before go-live |
| Access and portability | Support ticket or email to HOCO's contact, identity confirmed by an OTP to the account's phone | Super admin runs the personal-data export (JSON and PDF) — logged in the journal | 30 days (to validate) |
| Rectification | Profile screens for editable fields; support for the rest | Back-office edit, audited | 7 days |
| Objection to marketing | Notification preferences (NOT-06) | Immediate | Immediate |
| Erasure and account deletion | In-app « Supprimer mon compte » request and a web page (required by Google Play for apps with account creation) | Account closed; personal data anonymised except records under legal retention; confirmation sent | 30 days (to validate) |
| Withdrawal of geolocation consent (delegates) | Settings or HR | Location stops; the use of the delegate app without location is defined with HOCO's counsel | Immediate |
Processors and transfers
Every provider that handles personal data needs a contract with data-protection terms, a known data location and — when data leaves Algeria — the authorisation or legal basis that Law 18-07 requires (to validate with HOCO's counsel; hosting location is D-15).
| Processor (category) | Personal data handled | Location to document |
|---|---|---|
| Hosting and database (VPS) | Everything | Provider's data centre |
| Object storage and backups | Photos, documents, proofs, dumps | Provider's region |
| Google Firebase (FCM, App Distribution) | Push tokens, notification texts, testers' emails | Google infrastructure |
| Google Maps Platform | Store coordinates sent for geocoding | Google infrastructure |
| SMS gateway | Phone numbers, message texts | Gateway's infrastructure |
| Email provider | Staff and reseller emails, message content | Provider's region |
| Sentry | Technical error context (scrubbed of personal data) | Sentry region chosen at setup |
| GitHub (repository and documentation) | No production personal data | — |
Quality — NFR-09
Cahier §13.4 (maintenance) requires a maintainable platform; NFR-09.
| Layer | Tests | Gate |
|---|---|---|
| Server domain logic | Unit tests (Vitest): price resolution by customer type, promotion application, state machines, stock arithmetic (reservation, release, automatic restock on cancellation and return reception), credit exposure, permission guard, GPS distance | Line coverage ≥ 80 % on domain modules (proposed) |
| Server modules | Integration tests against a real PostgreSQL and Redis (CI services): every procedure's happy path, permission and scope denials, main error codes | All pass; overall coverage ≥ 60 % |
| API contract | OpenAPI snapshot diff on every pull request; breaking changes need the contract-breaking label and a note to Imad | No unlabeled breaking change |
| Database | prisma migrate diff shows no drift; migrations are additive (expand/contract) | Clean diff |
| Back-office and reseller web | Component tests for complex widgets; Playwright end-to-end smoke: sign-in, confirm an order, verify a payment, resolve a claim, receive a return, reseller checkout on the web | All pass on Chromium; weekly on Firefox and WebKit |
| Flutter apps | Unit tests (formatters, offline outbox, SSE reconnection), golden tests of hoco_ui widgets, integration tests of the ordering and visit flows on an emulator (MOB-15) | All pass; flutter analyze clean |
| Security | gitleaks, dependency audit, redaction test (NFR-8.2), permission sweep (ACL-5.1), no-quantity contract test (ACL-5.4) | All pass |
| Documentation | check-mdx on the docs site | No problem |
CI on every pull request runs: Biome lint and format, TypeScript type-check, the tests above, the contract diff, the migration check, image builds, gitleaks and the dependency audit (and, in hoco-mobile, dart format, flutter analyze, tests and an APK build). A pull request merges only when CI is green and the definition of done is met.
Verification plan
| Task | Verifies |
|---|---|
| SRV-04 · SRV-05 · SRV-06 · SRV-07 | NFR-8.1, SEC-0.5, SEC-1.x, SEC-2.x, ACL-5.1–5.3, SEC-4.2–4.4 |
| OPS-02 | NFR-2.4, SEC-3.x, SEC-4.1, SEC-5.1–5.3, SEC-0.7–0.11, NFR-8.4, NFR-8.6 |
| SRV-28 | NFR-1.1, 1.2, 1.4, 1.6, 1.9, 1.11, NFR-2.5, SEC-5.4, NFR-3.1–3.5, ANA-5.3, SEC-0.4, permission sweep |
| MOB-12 · MOB-13 · MOB-15 · MOB-17 | NFR-1.3, 1.5, 1.7, NFR-4.2, NFR-7.1–7.4 |
| DQA-02 · DQA-03 · DQA-04 · DQA-05 | NFR-4.5, NFR-5.x, NFR-6.10, NFR-7.5, NFR-7.8 |
| QA-01 · QA-02 | Every Demo item through the acceptance scenarios |
Deployment, training & maintenance
Requirements OPS-01 to OPS-06: environments, deployment, training, maintenance and support, acceptance and the pilot, within the three-month V1 frame.
Architecture and data
Application boundaries, shared packages and their dependency rules, the system of record per module, consistency, caching, file flows, the request lifecycle inside the server, errors, observability and configuration.