Skip to main content

Data Flow Document

Data Flow Document

Project: Drop{{PROJECT_NAME}} Version: 1.0{{VERSION}} Date: 2026-02-23{{DATE}} Author: Petter Graff, Senior Enterprise Architect{{AUTHOR}} Status: Draft | In Review | Approved Reviewers: Alem Bašić (CEO), John (AI Director){{REVIEWERS}} Classification: Public | Internal | Confidential | Restricted

Document History

Version Date Author Changes
0.1 2026-02-23{{DATE}} Petter Graff{{AUTHOR}} Initial draft from real architecture and schema docs

1. Data Flow Overview

System: Drop Payment Platform (ALAI Holding AS){{SYSTEM_NAME}} Data Owner: Alem Bašić, CEO — [email protected]{{DATA_OWNER_ROLE}} DPO Contact: [email protected] (DPO designation TBD — required before production launch per GDPR Art. 37){{DPO_EMAIL}}

Overview: Drop processes personal, financial, and compliance data for Norwegian residents (18+) using a PSD2 pass-through model. Data enters from three surfaces: user registration (BankID OIDC), transaction initiation (web + mobile clients), and compliance webhooks (Sumsub KYC). Data is stored in 19 PostgreSQL tables, with sensitive fields (national ID, IBAN) hashed or encrypted. Data exits through the REST API (user-facing), PISP calls (payment rails), and regulatory reporting (Finanstilsynet, Okokrim).

Critical Architectural Principle: Drop never holds customer funds. bank_accounts.balance is a cached AISP read from the user's actual bank — not a Drop balance. No wallet, no top-up.{{DESCRIBE_WHAT_DATA_FLOWS_THROUGH_SYSTEM}}

High-Level Data Flow

flowchart LR
    subgraph Inputs["Data Sources / Ingestion"]
        BID["BankIDU[Users OIDC\n(Identity +Web/App]
        age)"]API_IN[External WEB["WebAPI]
        App\n(Next.js)"]IMPORT[Bulk MOB["MobileImport]
        App\n(Expo)"]
        SUM["Sumsub Webhooks\n(KYC results)"]
        OB["Open Banking AISP\n(Balance reads)"]WEBHOOK[Webhooks]
    end

    subgraph Processing["DropProcessing API Processing"]
        AUTH["Auth Middleware\n(JWT verify, session check)"Layer"]
        VAL["InputValidation Validation\n(validateName,& sanitizeText,\nvalidateAmount,Sanitization]
        validatePhone)"]
        BIZ["TRANS[Business Logic\n(FeeLogic calc,/ FX,Transformation]
        disclosure,\nKYCENRICH[Data enforcement)"]
        AUDIT["Audit + Compliance\n(audit_log, AML rules, STR)"]Enrichment]
    end

    subgraph Storage["DropStorage Database (PostgreSQL 16)"Layer"]
        CORE["CoreDB[(Primary Domain\nusers, sessions,\ntransactions, bank_accounts,\nrecipients, merchants,\nnotifications, settings"DB\nPostgreSQL)]
        COMPLIANCE["Compliance Domain\naudit_log, aml_alerts,\nstr_reports, screening_results,\nconsents, data_access_requests,\ncomplaints"CACHE[(Cache\nRedis)]
        SYSTEM["System\nexchange_rates,BLOB[Object rate_limits,\ncardsStorage\nS3/Blob]
        (future),SEARCH[Search spending_limitsIndex\nElasticsearch]
        (future)"DW[Data Warehouse\n{{DW_TECH}}]
    end

    subgraph Outputs["EgressData Consumers / Data Consumers"Egress"]
        API_OUT["REST API\n(WebAPI]
        +REPORTS[Reports Mobile/ clients)"]Analytics]
        PISP_OUT["OpenEXPORT[Data BankingExport]
        PISP\n(PaymentTHIRD[Third-party initiationIntegrations]
        toEMAIL[Email user's/ bank)"]
        SEPA["SEPA/SWIFT\n(Remittance settlement)"]
        REG["Regulatory Reporting\nFinanstilsynet + Okokrim (STR)"]
        SENTRY["Sentry\n(Errors — PII masked)"]Notifications]
    end

    BIDU -->& AUTHAPI_IN & IMPORT & WEBHOOK --> VAL
    --> BIZ
    WEB & MOBVAL --> AUTHTRANS
    SUMTRANS --> VALENRICH
    OBENRICH --> COREDB BIZ& BLOB
    DB --> CORECACHE & COMPLIANCESEARCH
    & SYSTEM
    BIZDB --> AUDITDW
    AUDIT --> COMPLIANCE

    COREDB --> API_OUT CORE& REPORTS & EXPORT
    DW --> PISP_OUTREPORTS
    ENRICH --> SEPATHIRD
    COMPLIANCEDB --> REG
    BIZ --> SENTRYEMAIL

2. Data Sources & Ingestion

Source Type Protocol Volume (est. MVP)) Format PII? Validation
BankID OIDC ID tokenReal-time (per login)HTTPS OIDC callback100-1000 logins/dayJWT (signed)YES — pid, nameJWKS signature + issuer + nonce + age >= 18
Web appapplication user actionsusers Real-time HTTPS POST 500-5000 req/dayJSONYES — IBAN, amountSchema validation + validateName/validateAmount
Mobile app user actionsReal-timeHTTPS POST (Bearer)200-2000{{REQ_PER_DAY}} req/day JSON YES SameSchema as+ webbusiness rules
SumsubMobile webhooksapp users Event-drivenReal-time HTTPS POST (inbound) 10-100{{REQ_PER_DAY}} webhooks/req/day JSON YES — KYC result HMAC-SHA256Schema signature+ verificationbusiness rules
Open{{EXTERNAL_SYSTEM}} Banking AISPAPI On-demand + scheduledReal-time HTTPS GETWebhooks 4{{EVENTS_PER_DAY}} reads/account/events/day max JSON (Berlin Group) YES{{YES/NO}} HMAC IBAN,signature balance+ schema
CSV bulk importBatchFile upload{{IMPORTS_PER_DAY}} files/dayCSV{{YES/NO}}Column mapping + row validation
{{THIRD_PARTY_API}}PollingREST/HTTPS{{CALLS_PER_HOUR}}/hourJSON{{YES/NO}} Response schema validation + consent check

Ingestion Error Handling

Error Type Action Notification
BankID signature invalidReject — return jwks_verification_failed 502Sentry HIGH alert
Schema validation failure Reject — return 400 validation_error with fielderror details NoReturn alert400 (expected)to caller
SumsubDuplicate HMAC mismatchrecord RejectUpsert (prefer existing) or rejectLog, return 401Sentry HIGH alert — possible SUMSUB_SECRET_KEY rotation issue
Open Banking consent expiredReject AISP call — prompt user to re-linkIn-app notification409
PII fieldfields withcontain unexpected formatdataQuarantine + alertSlack #{{CHANNEL}}
Import file corrupted Reject +entire log (masked)file SentryEmail MEDIUMuploader alert+ error report

3. Data Transformations

3.1 Ingestion Transformations (before storage)

appliedin
Step Input Transformation Output Notes
1. PID hashingRaw pid from BankID (11 digits)SHA-256 hashusers.national_id_hashRaw pid NEVER stored
2. Name splittingname claim from BankID (e.g., "Ola Nordmann")Split on first spaceusers.first_name, users.last_nameApplied in bankid.ts:findOrCreateUser
3. Birthdate extractionpid digits DDMMYY + individual numberParse century + DOBusers.date_of_birth (ISO: YYYY-MM-DD)parseBirthdateFromPid() in bankid.ts
4. Input sanitizationSanitization Raw user text (name, description)input sanitizeText() — stripStrip HTML, trim whitespace Clean strings Prevents XSS;XSS
2. Normalization{{FIELD}}Lowercase + trimNormalized lib/middleware/validation.ts{{FIELD}}e.g., email normalization
3. EnrichmentUser IPGeoIP lookup{country, region, city}Third-party API call
4. PII masking{{PII_FIELD}}Hash / mask for logsMasked valueNever log raw PII
5. Balance conversionEncryption AISPSensitive balance in decimal NOK ("45230.00")fields Multiply by 100 → integer øreAES-256-GCM bank_accounts.balanceEncrypted (integer, øre)blob Avoids floating-point money arithmetic
6. Fee calculationSend amount (NOK)amount * 0.005 → round 2 decimalstransactions.feeApplied in transactions route before INSERT
7. Exchange rateAt application Send amount + rate from exchange_ratesamount * rate → round to whole unitstransactions.receive_amount
8. JWT issuanceuserId, email placeholder, roleSign with jose HS256 + JWT_SECRETdrop_token cookie (web) / Bearer JWT (mobile)24h (web) / 7d (mobile)layer

3.2 ETL Pipeline (to Data Warehouse)

Drop

flowchart doesLR
    notsubgraph haveExtract["Extract"]
        a separate data warehouse or ETL pipeline at MVP scale. All analytics queries run directly against the PGLOG[PostgreSQL replicaWAL / CDC]
        SCHED[Scheduled SQL Export]
    end

    subgraph Transform["Transform (when{{TRANSFORM_TOOL}})"]
        available).CLEAN[Data ACleaning]
        dataJOIN[Joins warehouse& isAggregations]
        plannedDEDUP[Deduplication]
        forANON[PII PhaseAnonymization]
    3end

    subgraph Load["Load"]
        DW[({{DATA_WAREHOUSE}})]
    end

    PGLOG --> CLEAN
    SCHED --> CLEAN
    CLEAN --> JOIN
    JOIN --> DEDUP
    DEDUP --> ANON
    ANON --> DW

Pipeline schedule: {{PIPELINE_SCHEDULE}} (10K+e.g., users).hourly incremental, daily full) Latency: Source to DW within {{MAX_LATENCY}} Tool: {{ETL_TOOL}} (e.g., dbt, Airbyte, custom)


4. Data Storage

automated
Storage System Technology Purpose Data Classification Encryption at Rest
Primary Database PostgreSQL{{DB_TECH}} 16 (AWS RDS){{VERSION}} All transactional and complianceTransactional data — 19 tables Restricted / Confidential AES-256 (RDS storage encryption, AWS-managed key){{KEY_MGMT}})
Development DatabaseCache SQLiteRedis 3 (/app/data/drop.db){{VERSION}} LocalHot devdata, only — no real PIIsessions Internal OS-level (developer machine)AES-256
SecretsObject Storage AWS Secrets Manager{{S3_COMPATIBLE}} JWT_SECRET,Files, BankIDdocuments, credentials, DB credentials, Sentry DSNmedia Critical{{CLASSIFICATION}} AES-256SSE-S3 (AWS/ KMS)SSE-KMS
BackupsSearch Index RDSElasticsearch Full-text backupssearchInternalTLS + snapshotsat-rest encryption
Data Warehouse{{DW}}Analytics, reportingAnonymized{{DW_ENCRYPTION}}
Backup Storage{{BACKUP_TECH}} Disaster recovery Restricted AES-256 (RDS)
ErrorAudit Logs Sentry{{LOG_STORAGE}} ErrorCompliance events/ (PIIaudit masked)trail InternalRestricted Sentry'sImmutable, encryption
App LogsAWS CloudWatchRequest/response logs (PII excluded)InternalCloudWatch encryption
Container imagesAWS ECRDocker images — no data, no secretsInternalECR encryption at restencrypted

5. Data Access Patterns

5.1 Read Patterns

Consumer Data Accessed Frequency Access Method Caching
Web app (GET /api/auth/me)application User profile, bank accounts, total balancesettings Per page loadrequest REST API Balance:Redis bank_accounts.balance_synced_at5min (6h staleness)TTL
Web app (GET /v1/transactions)application Transaction{{ENTITY}} list (paginated) Per page load REST API (paginated, limit 20)paginated) NoCDN cache+ — real-time from DBRedis
MobileReporting app (GET /v1/transactions)service TransactionAggregated listmetrics PerEvery page load1h RESTDW APIquery NoMaterialized cache
Mobile app (GET /v1/recipients)Saved recipientsPer send-money flowREST APINo cache
Web app (GET /v1/rates/:currency)Exchange ratePer amount-entryREST APIRate limited to 120/min — DB row readviews
Admin (GET /api/admin/users)dashboard UserRaw list with KYC statusrecords On demand REST API (admin-only)admin) No cache
External partner{{SUBSET_OF_DATA}}{{FREQUENCY}}REST API (scoped JWT){{CACHING}}

5.2 Write Patterns

to
Writer Data Written Frequency Write Method Consistency
AuthUser moduleactions (BankID callback)web) users,CRUD sessions, audit_logoperations Per loginuser action AtomicREST DB transactionAPI Strong (synchronous)
TransactionsBackground routeworker transactions,Aggregates, bank_accountscomputed (balance deduct), audit_log, notificationsPer paymentAtomic DB transactionStrong
Sumsub webhookusers.kyc_status, screening_results, audit_log, notificationsPer KYC eventAtomic DB transactionStrong (within Drop)
Rate limit middlewarerate_limits (count + reset_at)fields Every rate-limited request{{INTERVAL}} Direct DB write Eventual (cleanup every 100 calls)
AISPImport balance syncprocess bank_accounts.balance,Bulk bank_accounts.balance_synced_atrecords Up{{FREQUENCY}} Batch 4x/dayinsert Strong (per accountbatch)
Event consumerDenormalized cacheOn event Direct DB write Eventual (cached value)

6. Data Retention & Archival

(RDSautomated)
Data Category Retention Period Legal Basis Action at Expiry Automated?
User account data Duration of relationship + 5{{N}} years Contract (Finansavtaleloven) Soft delete → anonymize (nullify PII fields) PlannedAutomated (nightly jobjob)
Transaction records 5{{N}} years Legal obligation (Hvitvaskingsloven § 22){{REGULATION}}) Archive to cold storage (planned) Planned
AML alerts + STR reports5 yearsLegal obligation (Hvitvaskingsloven § 22)Retain — not deletable per AML lawNo (legal retention)Automated
Audit logs (audit_log) 5{{N}} years Legitimate interest (security + compliance)security) PurgeDelete PlannedAutomated
Session tokens (sessions) 7{{N}} hours/days max Technical necessity ExpiredAuto-expire expires_atvia rows pruned on loginTTL Yes (sessionRedis cleanup on login)TTL)
GDPRMarketing consentsconsent Until consent withdrawn Consent (GDPR Art. 6(1)(a)) Delete within 30{{N}} days of withdrawal Manual + planned jobautomated
KYCAnalytics screening resultsdata 5{{N}} yearsLegal obligationArchivePlanned
Notifications90 daysLegitimate interest (UX)DeletePlanned
Rate limit counters60 seconds (TTL = reset_at)Technical necessityAuto-cleaned every 100 callsYes (middleware cleanup)
Error logs (Sentry)90 daysanonymized) Legitimate interest Auto-purged by SentryDelete Yes (Sentry retention policy)Automated
RDSBackup backupsfiles 30{{N}} days (prod), 7 days (staging) Business continuity Overwrite (rolling) YesAutomated
Error logs{{N}} daysLegitimate interestDeleteAutomated

AMLRetention Override:schedule job: GDPR Art. 17 (right to erasure) is overridden by Hvitvaskingsloven § 22retention-policy.job.tstransactionruns anddaily KYCat data{{TIME}} mustUTC beArchival retainedtarget: 5 years regardless of user erasure request. Drop implements soft delete + anonymization (nullify email, first_name, last_name, phone) while retaining financial and compliance records.{{COLD_STORAGE_LOCATION}}


7. Data Quality Rules

7.1 Validation Rules

login
Field Rule Error Action Severity
users.national_id_hashemail SHA-256Valid hexRFC string5322 (64 chars)format Reject login CRITICAL
users.date_of_birthphone ISOE.164 date, age >= 18 yearsformat Reject HIGH
{{DATE_FIELD}}Not in futureRejectHIGH
{{AMOUNT_FIELD}}>= 0Reject CRITICAL
transactions.amount{{FK_FIELD}} 100References existing amount ≤ 50000 (NOK)record Reject payment CRITICAL
transactions.idempotency_key{{TEXT_FIELD}} UniqueMax (UNIQUE{{N}} index)characters Reject duplicate, return existingCRITICAL
recipients.countryOne of: RS, BA, PL, PK, TR, EUReject recipient creationHIGH
bank_accounts.ibanValid IBAN format (application-layer check)Reject bank linkingHIGH
users.emailPlaceholder format [email protected]N/A (generated)LOW
Input text fields (name, description)Sanitized (no HTML, max lengths per field)Reject with 422 MEDIUM

7.2 Data Quality Metrics

occurrenceduplicate ofaccounts
Metric Target CurrentAlert Threshold
Null rate on users.national_id_hashrequired fields 0% Any{{CURRENT}} > 0.1%
TransactionDuplicate idempotency violations preventedrate 100%< 0.01% Any{{CURRENT}} > slip-through0.1%
KYC webhook HMACSchema validation pass rate 100%> 99.9%{{CURRENT}} < 100% → alert ops99%
AISPETL balancepipeline stalenesssuccess > 24h0%rate > 99.5% {{CURRENT}} < → alert98%

8. PII Data Flow Mapping

8.1 PII Inventory

PII Category Fields Storage Location Encrypted? Access Controls Lawful Basis
NorwegianContact national IDinfo users.national_id_hashemail, phone, address PostgreSQLPrimary usersDB, tableEmail system SHA-256 one-way hashYes AuthRole-based middleware(user self + admin onlyContract + Legal obligation
Full nameusers.first_name, users.last_namePostgreSQL users tableAt-rest (RDS encryption)Auth middleware (own data) + adminadmin) Contract
Date of birthIdentity users.full_name, date_of_birth PostgreSQLPrimary users tableDB At-restYes (field-level) Auth middleware (own data) + adminContract + Legal obligation (age verification)
Phone numberusers.phonePostgreSQL users tableAt-restOwn data + adminRole-based Contract
IBAN / bank accountFinancial bank_accounts.iban, bank_accounts.account_number, recipients.bank_account{{PAYMENT_FIELD}} PostgreSQL{{PAYMENT_PROVIDER}} (tokenized) At-restTokenized OwnPCI datascope only (user_id FK enforced) Contract
Cached bank balanceBehavioral bank_accounts.balancelogin_history, click_events PostgreSQLAnalytics DB At-restOwn data onlyContractNo (AISP)
IP addressaudit_log.ip_address, consents.ip_address, rate_limits.keyPostgreSQLAt-restanonymized) Admin only Legitimate interest (security)
KYC documentsLocation Notip_address stored(→ by Dropgeo) SumsubLogs servers(masked) Sumsub handlesN/A Sumsub dashboardAdmin only LegalLegitimate obligationinterest
Deviceuser_agent, device_idAnalytics DBNoAdmin onlyLegitimate interest

8.2 PII Flow Diagram

flowchart TD
    USER([Norwegian Resident\nDataData Subject]) -->|"BankID OIDC\n(pid, name, birthdate)"|Provides| INGESTION[DropIngestion Auth Module]Layer]
    INGESTION -->|"SHA-256(pid)Validates =& national_id_hash\nname → first/last_name\nbirthdate from pid"|encrypts| DB[(PostgreSQL\nusersPrimary table\DB\nPII encrypted at rest)]
    DB -->|"OwnPseudonymized| profileDW[(Data only\n(JWT-gated)"|Warehouse\nNo API_OUT["RESTdirect API\n/api/auth/me"PII)]
    DB -->|"national_id_hashMasked only\n(neverin rawlogs| pid)"|LOGS[Log AUDIT[audit_log]Aggregator]
    USERDB -->|"DocumentTokenized| upload"|PAYMENT[Payment SUMSUB_SDK[SumsubProvider\nPCI SDKscope]
    Widget]
    SUMSUB_SDKDB -->|"ApplicantExplicit data\n+consent| documentEMAIL[Email images"|Provider\nEmail SUMSUB[(Sumsub+ Servers\nKYCname documentsonly]
    stored)]
    SUMSUBDB -->|"kyc_statusRight result\n(noto rawerasure| docDELETION[Anonymization data)"|Service]
    DROP_WEBHOOK[Drop Webhook Handler]
    DROP_WEBHOOKDELETION -->|"kyc_status update\nscreening_result"|Anonymized| DB
    DB -->|"GDPRAudit Art.trail| 17AUDIT[Audit erasure\n(softLog\nRestricted delete + anonymize)"| ANONYMIZE[Anonymization\nNullify: email, first_name,\nlast_name, phone\nRetain: transactions, AML (5yr)]
    ANONYMIZE --> DB

    DB -->|"PISP: amount + IBAN\n(user's own bank)"| PISP_OUT["Open Banking PISP\n(Neonomics → ASPSP)"]
    PISP_OUT -->|"Execute transfer"| BANK[(User's Bank\nmoney always here)]

    DB -.->|"STR reports\n(hvitvaskingsloven)"| REG["Okokrim / EFE\n(Regulatory)"]access]

    style DB fill:#ffcccc
    style SUMSUBDW fill:#ccffcc
    style LOGS fill:#ffffcc
    style BANK fill:#ccffcc
    style REGAUDIT fill:#ffcccc

9. Cross-Border Data Transfer

Transfer From To Data Category Mechanism DPA Signed?DPA/SCCs?
KYC applicant data{{TRANSFER_1}} NorwayEU (Drop){{COUNTRY}}) InternationalUS (Sumsub){{PROVIDER}}) Name, DOB, document images{{DATA_CATEGORY}} Standard Contractual Clauses (SCCs) TBDYesrequiredsigned before production{{DATE}}
Error events{{TRANSFER_2}} Norway (Drop)EU USA (Sentry){{COUNTRY}} Error stack traces (PII masked){{DATA_CATEGORY}} StandardAdequacy Contractual Clauses (SCCs)Sentry DPA via ToS
BankID authNorway (user browser)Norway (BankID Norge)OIDC tokens, pidNorwegian entity — no cross-border transferdecision N/A
Neonomics AISP/PISP{{TRANSFER_3}} Norway (Drop API)EU Norway/EEA (Neonomics){{COUNTRY}} IBAN, balance, payment data{{DATA_CATEGORY}} EEABinding entityCorporate — adequacyRules DPA required in contract{{YES/NO}}

Third-party processors with data access:

Processor Service Data Accessed DPA Signed Location
Sumsub{{PROCESSOR_1}} KYC/AML verification{{SERVICE}} applicantId, external user ID, KYC result (documents stored by Sumsub, not Drop){{DATA}} RequiredYes International{{LOCATION}}
Sentry{{PROCESSOR_2}} Error tracking{{SERVICE}} Error messages (PII must be masked before capture){{DATA}} Via Sentry ToS DPAYes USA (SCCs)
AWSCloud hostingAll Drop data (encrypted at rest)AWS DPAeu-north-1 (Stockholm) — EEA
NeonomicsOpen Banking aggregatorIBAN, balance, payment detailsRequired in commercial contractNorway / EEA{{LOCATION}}

10. Data Lineage Tracking

Lineage tool: audit_log{{LINEAGE_TOOL}} table(e.g., Apache customAtlas, implementationDataHub, custom) Coverage: AllPrimary user-triggeredDB data+ mutations capturedDW

Lineage Events Captured

{
  "id"eventType": "audit_<hex16>"DATA_WRITE",
  "user_id"timestamp": "usr_abc123"ISO8601",
  "actor": "system/user-id",
  "action": "transaction.createCREATE | kyc.approvedUPDATE | session.createDELETE | ...EXPORT | IMPORT",
  "resource": {
    "type": "{{ENTITY}}",
    "resource_type"id": "transactionUUID"
  |},
  user"fields_modified": | session | ...["{{field1}}", "resource_id"{{field2}}"],
  "sourceSystem": "tx_abc123"{{SOURCE}}",
  "ip_address"traceId": "192.168.1.1",
  "created_at": "2026-02-23T10:00:00.000Z"UUID"
}

Actions tracked in audit_log:

  • session.create, session.revoke, session.revoke_all
  • transaction.create, transaction.status_update
  • kyc.initiated, kyc.approved, kyc.rejected
  • qr_payment.create
  • user.delete_account, user.data_export, user.data_export_request
  • complaint.create
  • bankid.login, bankid.callback_error

11. Backup & Recovery for Data

Storage Backup Method Frequency Retention RTO RPO Test Frequency
PostgreSQLPrimary (RDS prod)DB Continuous automatedWAL backups (PITR)archiving + daily snapshots Continuous / Daily 30 days 1 hour1h 5 minutes5min Monthly
PostgreSQLObject (RDS staging)Storage AutomatedCross-region backupsreplication DailyContinuous 730 days 2 hours4h 24 hours1h Quarterly
SQLiteData (dev)Warehouse Git-ignored; no backup (dev-only)Snapshot N/ADaily N/A14 days Rebuild from seed8h N/A24h N/AQuarterly
SecretsRedis (Secrets Manager)Cache AWS-managedRDB replicationsnapshots ContinuousEvery 15min Indefinite (versioned)24h < 5 min (create new)15min N/A15min N/AMonthly

PostgreSQL Point-in-Time Recovery:

aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier drop-prod \
  --target-db-instance-identifier drop-prod-restored \
  --restore-time "2026-02-23T10:00:00Z"

Last backup test: TBD{{DATE}}RequiredResult: before production launch{{PASS/FAIL}} Recovery runbook: docs/dr-runbook.md{{LINK_TO_RUNBOOK}}


Approval

Role Name Date Signature
Author Petter Graff 2026-02-23
Data Owner Alem Bašić (CEO)
DPO / Privacy TBD — required before production
Security
Tech Lead John (AI Director)