Skip to main content

Data Flow Document

Data Flow Document

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

Document History

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

1. Data Flow Overview

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

Overview: {{DESCRIBE_WHAT_DATA_FLOWS_THROUGH_SYSTEM}}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.

High-Level Data Flow

flowchart LR
    subgraph Inputs["Data Sources / Ingestion"]
        U[UsersBID["BankID OIDC\n(Identity Web/App]+ API_IN[Externalage)"]
        API]WEB["Web IMPORT[BulkApp\n(Next.js)"]
        Import]MOB["Mobile WEBHOOK[Webhooks]App\n(Expo)"]
        SUM["Sumsub Webhooks\n(KYC results)"]
        OB["Open Banking AISP\n(Balance reads)"]
    end

    subgraph Processing["ProcessingDrop Layer"API Processing"]
        AUTH["Auth Middleware\n(JWT verify, session check)"]
        VAL[Validation"Input &Validation\n(validateName, Sanitization]sanitizeText,\nvalidateAmount, TRANS[validatePhone)"]
        BIZ["Business LogicLogic\n(Fee /calc, Transformation]FX, ENRICH[Datadisclosure,\nKYC Enrichment]enforcement)"]
        AUDIT["Audit + Compliance\n(audit_log, AML rules, STR)"]
    end

    subgraph Storage["StorageDrop Layer"Database (PostgreSQL 16)"]
        DB[(PrimaryCORE["Core DB\nPostgreSQL)Domain\nusers, sessions,\ntransactions, bank_accounts,\nrecipients, merchants,\nnotifications, settings"]
        CACHE[(Cache\nRedis)COMPLIANCE["Compliance Domain\naudit_log, aml_alerts,\nstr_reports, screening_results,\nconsents, data_access_requests,\ncomplaints"]
        BLOB[ObjectSYSTEM["System\nexchange_rates, Storage\nS3/Blob]rate_limits,\ncards SEARCH[Search(future), Index\nElasticsearch]spending_limits DW[Data Warehouse\n{{DW_TECH}}(future)"]
    end

    subgraph Outputs["Data ConsumersEgress / Egress"Data Consumers"]
        API_OUT["REST API]API\n(Web REPORTS[Reports+ /Mobile Analytics]clients)"]
        EXPORT[DataPISP_OUT["Open Export]Banking THIRD[Third-partyPISP\n(Payment Integrations]initiation EMAIL[Emailto /user's Notifications]bank)"]
        SEPA["SEPA/SWIFT\n(Remittance settlement)"]
        REG["Regulatory Reporting\nFinanstilsynet + Okokrim (STR)"]
        SENTRY["Sentry\n(Errors — PII masked)"]
    end

    UBID &--> API_IN & IMPORT & WEBHOOKAUTH --> VAL VAL--> BIZ
    WEB & MOB --> TRANSAUTH
    TRANSSUM --> ENRICHVAL
    ENRICHOB --> DBCORE

    & BLOB
    DBBIZ --> CACHECORE & SEARCHCOMPLIANCE DB& SYSTEM
    BIZ --> DWAUDIT
    DBAUDIT --> COMPLIANCE

    CORE --> API_OUT
    & REPORTS & EXPORT
    DWCORE --> REPORTS
    ENRICHPISP_OUT --> THIRDSEPA
    DBCOMPLIANCE --> EMAILREG
    BIZ --> SENTRY

2. Data Sources & Ingestion

YES
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 applicationapp usersuser actions Real-time HTTPS POST {{REQ_PER_DAY}}500-5000 req/dayJSONYES — IBAN, amountSchema validation + validateName/validateAmount
Mobile app user actionsReal-timeHTTPS POST (Bearer)200-2000 req/day JSON YES SchemaSame +as business rulesweb
MobileSumsub app userswebhooks Real-timeEvent-driven HTTPS POST (inbound) {{REQ_PER_DAY}}10-100 req/webhooks/day JSON YES — KYC result SchemaHMAC-SHA256 +signature business rulesverification
{{EXTERNAL_SYSTEM}}Open APIBanking AISP Real-timeOn-demand + scheduled WebhooksHTTPS GET {{EVENTS_PER_DAY}}4 events/reads/account/day max JSON (Berlin Group) {{YES/NO}} HMAC signatureIBAN, + 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}}balance 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 errorfield details ReturnNo 400alert to caller(expected)
DuplicateSumsub recordHMAC mismatch UpsertReject (prefer existing)return or reject401 Log,Sentry returnHIGH 409alert — possible SUMSUB_SECRET_KEY rotation issue
Open Banking consent expiredReject AISP call — prompt user to re-linkIn-app notification
PII fieldsfield containwith unexpected dataQuarantine + alertSlack #{{CHANNEL}}
Import file corruptedformat Reject entire+ filelog (masked) EmailSentry uploaderMEDIUM + error reportalert

3. Data Transformations

3.1 Ingestion Transformations (before storage)

XSS;applied layer
Step Input Transformation Output Notes
1. SanitizationPID 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 sanitization Raw user inputtext (name, description) StripsanitizeText() — strip HTML, trim whitespace Clean strings Prevents XSS
2. Normalization{{FIELD}}Lowercase + trimNormalizedin {{FIELD}}lib/middleware/validation.tse.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. EncryptionBalance conversion SensitiveAISP fieldsbalance in decimal NOK ("45230.00") AES-256-GCMMultiply by 100 → integer øre Encryptedbank_accounts.balance blob(integer, øre) AtAvoids floating-point money arithmetic
6. Fee calculationSend amount (NOK)amount * 0.005 → round 2 decimalstransactions.feeApplied in transactions route before INSERT
7. Exchange rate 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)

3.2 ETL Pipeline

Drop does not have a separate data warehouse or ETL pipeline at MVP scale. All analytics queries run directly against the PostgreSQL replica (towhen Dataavailable). Warehouse)A

flowchartdata LRwarehouse subgraphis Extract["Extract"]planned PGLOG[PostgreSQLfor WALPhase / CDC]
        SCHED[Scheduled SQL Export]
    end

    subgraph Transform["Transform3 ({{TRANSFORM_TOOL}})"]10K+ CLEAN[Data Cleaning]
        JOIN[Joins & Aggregations]
        DEDUP[Deduplication]
        ANON[PII Anonymization]
    end

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

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

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


4. Data Storage

RDS
Storage System Technology Purpose Data Classification Encryption at Rest
Primary Database {{DB_TECH}}PostgreSQL {{VERSION}}16 (AWS RDS) TransactionalAll transactional and compliance data — 19 tables Restricted / Confidential AES-256 ({{KEY_MGMT}})RDS storage encryption, AWS-managed key)
CacheDevelopment Database RedisSQLite {{VERSION}}3 (/app/data/drop.db) HotLocal data,dev sessionsonly — no real PII Internal AES-256OS-level (developer machine)
Object StorageSecrets {{S3_COMPATIBLE}}AWS Secrets Manager Files,JWT_SECRET, documents,BankID mediacredentials, DB credentials, Sentry DSN {{CLASSIFICATION}}Critical SSE-S3AES-256 /(AWS SSE-KMSKMS)
Search IndexBackups Elasticsearch Full-textautomated searchInternalTLSbackups + at-rest encryption
Data Warehouse{{DW}}Analytics, reportingAnonymized{{DW_ENCRYPTION}}
Backup Storage{{BACKUP_TECH}}snapshots Disaster recovery Restricted AES-256 (RDS)
AuditError Logs {{LOG_STORAGE}}Sentry ComplianceError /events audit(PII trailmasked) RestrictedInternal Immutable,Sentry's encryptedencryption
App LogsAWS CloudWatchRequest/response logs (PII excluded)InternalCloudWatch encryption
Container imagesAWS ECRDocker images — no data, no secretsInternalECR encryption at rest

5. Data Access Patterns

5.1 Read Patterns

Consumer Data Accessed Frequency Access Method Caching
Web applicationapp (GET /api/auth/me) User profile, settingsbank accounts, total balance Per requestpage load REST API RedisBalance: 5minbank_accounts.balance_synced_at TTL(6h staleness)
Web applicationapp (GET /v1/transactions) {{ENTITY}}Transaction list (paginated)Per page loadREST API (paginated, limit 20)No cache — real-time from DB
Mobile app (GET /v1/transactions)Transaction list Per page load REST API (paginated) CDNNo + Rediscache
ReportingMobile serviceapp (GET /v1/recipients) AggregatedSaved metricsrecipients EveryPer 1hsend-money flow DWREST queryAPI MaterializedNo viewscache
Web app (GET /v1/rates/:currency)Exchange ratePer amount-entryREST APIRate limited to 120/min — DB row read
Admin dashboard(GET /api/admin/users) RawUser recordslist with KYC status On demand REST API (admin)admin-only) No cache
External partner{{SUBSET_OF_DATA}}{{FREQUENCY}}REST API (scoped JWT){{CACHING}}

5.2 Write Patterns

Up4x/day
Writer Data Written Frequency Write Method Consistency
UserAuth actionsmodule (web)BankID callback) CRUDusers, operationssessions, audit_log Per user actionlogin RESTAtomic APIDB transaction Strong (synchronous)
BackgroundTransactions workerroute Aggregates,transactions, computedbank_accounts fields(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) Every {{INTERVAL}}rate-limited request Direct DB write Eventual (cleanup every 100 calls)
ImportAISP processbalance sync Bulkbank_accounts.balance, recordsbank_accounts.balance_synced_at {{FREQUENCY}} Batchto insert Strong (per batch)
Event consumerDenormalized cacheOn eventaccount Direct DB write Eventual (cached value)

6. Data Retention & Archival

Yes(RDS
Data Category Retention Period Legal Basis Action at Expiry Automated?
User account data Duration of relationship + {{N}}5 years Contract (Finansavtaleloven) Soft delete → anonymize (nullify PII fields) AutomatedPlanned (nightly job)job
Transaction records {{N}}5 years Legal obligation ({{REGULATION}})Hvitvaskingsloven § 22) Archive to cold storage (planned) AutomatedPlanned
AML alerts + STR reports5 yearsLegal obligation (Hvitvaskingsloven § 22)Retain — not deletable per AML lawNo (legal retention)
Audit logs (audit_log) {{N}}5 years Legitimate interest (security)security + compliance) DeletePurge AutomatedPlanned
Session tokens (sessions) {{N}}7 hours/days max Technical necessity Auto-expireExpired viaexpires_at TTLrows pruned on login Yes (Redissession TTL)cleanup on login)
MarketingGDPR consentconsents Until consent withdrawn Consent (GDPR Art. 6(1)(a)) Delete within {{N}}30 days of withdrawal Manual + automatedplanned job
AnalyticsKYC datascreening results {{N}}5 yearsLegal obligationArchivePlanned
Notifications90 daysLegitimate interest (anonymized)UX)DeletePlanned
Rate limit counters60 seconds (TTL = reset_at)Technical necessityAuto-cleaned every 100 callsYes (middleware cleanup)
Error logs (Sentry)90 days Legitimate interest DeleteAuto-purged by Sentry AutomatedYes (Sentry retention policy)
BackupRDS filesbackups {{N}}30 days (prod), 7 days (staging) Business continuity Overwrite (rolling) Automated
Error logs{{N}} daysLegitimate interestDeleteAutomatedautomated)

RetentionAML schedule job:Override: retention-policy.job.tsGDPR Art. 17 (right to erasure) is overridden by Hvitvaskingsloven § 22runstransaction dailyand atKYC {{TIME}}data UTCmust Archivalbe target:retained {{COLD_STORAGE_LOCATION}}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.


7. Data Quality Rules

7.1 Validation Rules

with
Field Rule Error Action Severity
emailusers.national_id_hash ValidSHA-256 RFChex 5322string format(64 chars) Reject login CRITICAL
phoneusers.date_of_birth E.164ISO formatdate, age >= 18 years Reject loginCRITICAL
transactions.amount100 ≤ amount ≤ 50000 (NOK)Reject paymentCRITICAL
transactions.idempotency_keyUnique (UNIQUE index)Reject duplicate, return existingCRITICAL
recipients.countryOne of: RS, BA, PL, PK, TR, EUReject recipient creation HIGH
{{DATE_FIELD}}bank_accounts.iban NotValid inIBAN futureformat (application-layer check) Reject bank linking HIGH
{{AMOUNT_FIELD}}users.email >=Placeholder 0format [email protected] RejectN/A (generated) CRITICALLOW
Input text fields ({{FK_FIELD}}name, description) ReferencesSanitized existing(no recordHTML, max lengths per field) Reject CRITICAL
{{TEXT_FIELD}}Max {{N}} charactersReject422 MEDIUM

7.2 Data Quality Metrics

AnyAny of
Metric Target CurrentAlert Threshold
Null rate on required fieldsusers.national_id_hash 0% {{CURRENT}} > 0.1%occurrence
DuplicateTransaction rateidempotency violations prevented < 0.01%100% {{CURRENT}} >duplicate 0.1%slip-through
SchemaKYC webhook HMAC validation pass rate > 99.9%{{CURRENT}}100% < 99%100% → alert ops
ETLAISP pipelinebalance successstaleness rate> 24h0% > 99.5% {{CURRENT}} <accounts 98%→ alert

8. PII Data Flow Mapping

8.1 PII Inventory

PII Category Fields Storage Location Encrypted? Access Controls Lawful Basis
ContactNorwegian infonational ID emailusers.national_id_hashPostgreSQL users tableSHA-256 one-way hashAuth middleware + admin onlyContract + Legal obligation
Full nameusers.first_name, phone, addressusers.last_name PrimaryPostgreSQL DB,users Email systemtable YesAt-rest (RDS encryption) Role-basedAuth middleware (userown selfdata) + admin)admin Contract
IdentityDate of birth full_name, users.date_of_birth PrimaryPostgreSQL DBusers table Yes (field-level)At-rest Role-basedAuth middleware (own data) + adminContract + Legal obligation (age verification)
Phone numberusers.phonePostgreSQL users tableAt-restOwn data + admin Contract
FinancialIBAN / bank account {{PAYMENT_FIELD}}bank_accounts.iban, bank_accounts.account_number, recipients.bank_account {{PAYMENT_PROVIDER}} (tokenized)PostgreSQL TokenizedAt-rest PCIOwn scopedata only (user_id FK enforced) Contract
BehavioralCached bank balance login_historybank_accounts.balancePostgreSQLAt-restOwn data onlyContract (AISP)
IP addressaudit_log.ip_address, click_eventsconsents.ip_address, rate_limits.key Analytics DBPostgreSQL No (anonymized)At-rest Admin only Legitimate interest (security)
LocationKYC documents ip_addressNot (→stored geo)by Drop LogsSumsub (masked)servers N/ASumsub handles AdminSumsub dashboard only LegitimateLegal interest
Deviceuser_agent, device_idAnalytics DBNoAdmin onlyLegitimate interestobligation

8.2 PII Flow Diagram

flowchart TD
    USER([DataNorwegian Resident\nData Subject]) -->|Provides|"BankID OIDC\n(pid, name, birthdate)"| INGESTION[IngestionDrop Layer]Auth Module]
    INGESTION -->|Validates"SHA-256(pid) &= encrypts|national_id_hash\nname → first/last_name\nbirthdate from pid"| DB[(PrimaryPostgreSQL\nusers DB\table\nPII encrypted at rest)]
    DB -->|Pseudonymized|"Own DW[(Dataprofile Warehouse\nNoonly\n(JWT-gated)"| directAPI_OUT["REST PII)API\n/api/auth/me"]
    DB -->|Masked"national_id_hash inonly\n(never logs|raw LOGS[Logpid)"| Aggregator]AUDIT[audit_log]

    DBUSER -->|Tokenized|"Document PAYMENT[Paymentupload"| Provider\nPCISUMSUB_SDK[Sumsub scope]SDK DBWidget]
    SUMSUB_SDK -->|Explicit"Applicant consent|data\n+ EMAIL[Emaildocument Provider\nEmailimages"| +SUMSUB[(Sumsub nameServers\nKYC only]documents DBstored)]
    SUMSUB -->|Right"kyc_status toresult\n(no erasure|raw DELETION[Anonymizationdoc Service]data)"| DELETIONDROP_WEBHOOK[Drop Webhook Handler]
    DROP_WEBHOOK -->|Anonymized|"kyc_status update\nscreening_result"| DB

    DB -->|Audit"GDPR trail|Art. AUDIT[Audit17 Log\nRestrictederasure\n(soft access]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)"]

    style DB fill:#ffcccc
    style DWSUMSUB fill:#ffffcc
    style BANK fill:#ccffcc
    style LOGS fill:#ffffcc
    style AUDITREG fill:#ffcccc

9. Cross-Border Data Transfer

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

Third-party processors with data access:

Processor Service Data Accessed DPA Signed Location
{{PROCESSOR_1}}Sumsub {{SERVICE}}KYC/AML verification {{DATA}}applicantId, external user ID, KYC result (documents stored by Sumsub, not Drop) YesRequired {{LOCATION}}International
{{PROCESSOR_2}}Sentry {{SERVICE}}Error tracking {{DATA}}Error messages (PII must be masked before capture) YesVia Sentry ToS DPA {{LOCATION}}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

10. Data Lineage Tracking

Lineage tool: {{LINEAGE_TOOL}}audit_log (e.g.,table Apache Atlas,custom DataHub, custom)implementation Coverage: PrimaryAll DBuser-triggered +data DWmutations captured

Lineage Events Captured

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

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
PrimaryPostgreSQL DB(RDS prod) Continuous WALautomated archivingbackups (PITR) + daily snapshots Continuous / Daily 30 days 1h1 hour 5min5 minutes Monthly
ObjectPostgreSQL Storage(RDS staging) Cross-regionAutomated replicationbackups ContinuousDaily 307 days 4h2 hours 1h24 hours Quarterly
DataSQLite Warehouse(dev) SnapshotGit-ignored; no backup (dev-only) DailyN/A 14 daysN/A 8hRebuild from seed 24hN/A QuarterlyN/A
RedisSecrets Cache(Secrets Manager) RDBAWS-managed snapshotsreplication Every 15minContinuous 24hIndefinite (versioned) 15min< 5 min (create new) 15minN/A MonthlyN/A

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: {{DATE}}TBDResult:Required {{PASS/FAIL}}before production launch Recovery runbook: {{LINK_TO_RUNBOOK}}docs/dr-runbook.md


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)