Skip to content
CIQRA
All legal documents

Data Retention & Personal-Data Breach Notification Procedure


IN FORCE. See 00-README. Internal procedure + merchant/counsel reference. Supports the Privacy Policy and DPA.

Legal basis. Every provision below rests on one of three things: a measured fact about the platform (cited to file and line), a rule of law (cited to the instrument and article in the Legal Basis Register), or a commercial choice CIQRA has made where the law leaves it open. Prepared and adopted by CIQRA OÜ.

Owner: CIQRA OÜ · privacy@ciqra.com · In force from: 2026-08-09 · Version: 1.0 · Adopted by: CIQRA OÜ


Part A — Data Retention

A.0 🔴 Read this before reading the matrix

The retention matrix in A.2 is a statement of intended policy, not a description of an implemented control. The distinction is load-bearing and this document previously blurred it.

🔴 Corrected 2026-08-10 — the previous census in this section was wrong. It said the platform ran "exactly five background workers" and that none of them enforced a retention period. Re-measured against the same tree: the platform runs 27 background workers (declared count — legalgate verifies it against the tree; see the note below), and seven of them delete data on a clockRetentionService, BackupRetentionWorker, TenantPurgeWorker, AuditRetentionWorker, from 2026-08-11 ErasureDueWorker, from 2026-09-03 McpConfirmationSweeper, and from 2026-09-04 PlatformRetentionWorker.

🔴 25 → 27 on 2026-09-04, and it is TWO movements, not one. Attributed by measurement, because the note two entries below says in so many words that a tripwire cannot tell you which change moved it.

① 25 → 26 was NOT caused by this plan and was already red at HEAD. The gate's own regex was run over the tree with this plan's new worker file removed: it returns 26 against a declared 25. Every worker class file was then dated by its adding commit, and one postdates the 2026-09-03 declaration: OrderSettlementWorker (Ciqra.Api/Maintenance/), added 2026-09-04 in 3231d3384. It landed the same day this was written, its HostedServiceGate row landed with it, and only this table was not revisited — so the hosted-service census stayed green while the legal declaration went stale. It is not a clock deleter: it drains the durable order-settlement queue and dispatches outward.

🔴 And "not a clock deleter" is the finding, not the dismissal. Grepped across the worker and its dispatcher for Remove/Delete/ExecuteDelete/Purge: nothing. So order_settlement_notices has no retention path of ANY kind — a delivered notice stays, and a dead-lettered one stays forever carrying its order reference and its failure reason. It appears in neither §A.2 nor PlatformRetention's register, which is measurable rather than asserted: grep -rn "order_settlement_notices" over this file and Ciqra.Api/Retention/ returns nothing. That is another unbounded platform table and it belongs on PA-0017's list — recorded here rather than noticed and forgotten, which is the failure mode this very section has now caught itself in five times. 🔑 This is the fifth time this census has been wrong, and the first time the drift was attributed by dating the files rather than by assuming the newest plan caused it. Moving the number by one would have absorbed another plan's drift into this one silently — the failure the 2026-08-17 note describes, happening again.

② 26 → 27 IS this plan, and the new worker is a clock deleter, the seventh. PlatformRetentionWorker (Ciqra.Api/Retention/) is PA-0017's answer to this whole section: it is the clock behind §A.2, and it holds no period of its own. Every row it sweeps is declared in Ciqra.Api.Retention.PlatformRetention, and every row of that register is matched against the §A.2 matrix read out of this file at test time — so a promise cannot be written below without acquiring a job or a written exemption, and a period cannot be enforced without appearing here.

🔴 Landing it turned up a defect in one of the six above, and the defect is worth more than the seventh worker. AuditRetentionWorker deleted nothing. Every swept table carries FORCE ROW LEVEL SECURITY with the policy tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid, and the application connects as a NOSUPERUSER NOBYPASSRLS role. The worker resolved one AppDbContext from a bare background scope, where the ambient tenant is none — so the setting was empty, NULLIF gave NULL, the comparison was NULL for every row, and the DELETE matched nothing while returning 0 and logging "nothing past the window", which is the same sentence it prints when the window really is empty.

🔑 Its test could not have seen it, and that is the transferable part. The test drives the sweep through _fixture.NewAppContext(TenantA) — a context that has already entered a tenant scope, which the worker never has. It proved the RULE (the old row goes, the fresh row stays) against a connection the worker does not use. A sweep handed a scope its caller never holds measures the statement, not the job. Both workers now run tenant by tenant, and the test that says so drives the pass the worker runs rather than the statement inside it.

⚠️ Declared limit, unchanged: _count_workers() collects class NAMES into a set and the tree holds two different classes called ScheduledPublishActivator — one in Ciqra.Api/Maintenance/, one in Ciqra.Modules.Cms.Workflow/Application/ — so the tree really holds 28 worker declarations and this check can only ever see 27. A new worker reusing an existing worker's class name still lands without moving this number.

🟢 The trap wire did its job on 2026-08-11. The declared count was 21 and the tree had 22: ErasureDueWorker landed with PA-0009's scheduling half, and it is a clock deleter — it sweeps hourly and carries out erasures whose grace period has expired. Nobody would have come back to this table for it; the check made somebody. 🔑 This is the fourth time this census was wrong and the first time it was caught by something other than a person re-reading the tree. The earlier count was an undercount, and an undercount is not a harmless error here: it hid the fact that periods are already being enforced which this document never declared, and that one enforced period contradicts a period this document promises.

🟢 And again on 2026-08-17, twice over — 22 → 24. TR-04 added IysReconciliationWorker (Ciqra.Modules.Notifications/Iys/), which syncs each Turkish merchant's İYS consent state so a commercial message can be refused when the recipient has opted out. It is not a clock deleter: it upserts consent rows and deletes nothing, so the five above are still five.

⚠️ But it accounted for only one of the two. When this count was re-measured the tree already held 23, not the declared 22: ComplaintAgeingWorker landed on 2026-08-11, the day after this declaration was written, and nobody came back to this table for it. It is not a clock deleter either. So the gate had been red on a drift that TR-04 did not cause and would have gone on being read as TR-04's if the number had simply been moved by one. 🔑 A tripwire only reports that a count moved; which change moved it is a question the tripwire cannot answer, and answering it by assumption is how one plan's number quietly absorbs another plan's defect.

🟢 And again on 2026-09-03 — 24 → 25, and this time the new worker is a CLOCK DELETER, the sixth. McpConfirmationSweeper (Ciqra.Api/Maintenance/) deletes lapsed MCP confirmation challenges hourly, one day past their expiry (McpConfirmationRetention.Grace).

🔴 It is here because the data had no retention at all and no erasure path either. A challenge row holds up to 8 KB of server-computed preview naming the values a merchant is being asked to approve, so a connector tool acting on an order or a customer can quote a data subject in it. mcp_confirmation_challenges carries none of the link columns the subject-access build and the erasure sweep discover categories by (customer_id, owner_id, order_id, the e-mail family), so it was invisible to a subject-access request and unreachable by erasure — while ErasureResidueScanner, which discovers by "any tenant-scoped table with a text column", matched the address inside the preview and reported residue on every erasure that no code path could clear. ExpiresAt gated answerability only; nothing deleted the row, ever.

🔑 So for this table the worker IS the retention guarantee — there is nothing behind it. That is also why the answer was a deletion rather than a link column: a link column would have made the incidental personal data more queryable while still storing it indefinitely. The table is listed in dsar-unreached-tables.baseline.txt on the strength of this worker existing, and that entry says so; the two must be read together, and deleting the worker without moving that entry is the failure mode to watch for.

⚠️ This one moved the count by one and the deleter column by one, which is the case the note above warns is rare. It is stated explicitly so the next reader does not have to re-derive it: the drift from 24 to 25 is caused entirely by this worker. Measured rather than assumed — the gate's own regex was run over the tree twice, once as it stands (25) and once with this file removed (24) — because the note above says in so many words that a tripwire cannot tell you which change moved it.

🔴 And running it turned up a blind spot in the tripwire itself, which is worth more than the number. _count_workers() collects class NAMES into a set, and the tree contains two different classes both called ScheduledPublishActivator — one in Ciqra.Api/Maintenance/, one in Ciqra.Modules.Cms.Workflow/Application/. So the tree really holds 26 worker classes and this check can only ever see 25. It is not wrong about drift, which is all it claims to measure, but a new worker that happened to reuse an existing worker's class name would land without moving this number at all — the one way a worker can still arrive here unnoticed. Left as measured rather than "fixed" by switching to a count of declaration sites: that would change the number this document declares for a reason unrelated to any worker landing, and the count's whole value is that a move means something.

A.0c 🟢 Scheduling an erasure starts a clock the subject must be told about — one month · BUILT 2026-08-11, with one seam open

Where a data subject asks CIQRA to erase their data, CIQRA tells them what was done and when, within one month of the request. Where the erasure is scheduled rather than immediate, that reply states the date it will take effect.

🔒 CIQRA's determination — the third limb of PA-0009, and the one that is a duty rather than a build

② Rule of law. GDPR Art. 12(3) requires the controller to provide information on action taken on a request without undue delay and in any event within one month of receipt, extendable by two further months for complexity, and the subject must be told of any extension, with reasons, within that first month. Art. 12(4) adds that if the controller does not act, it must say so within the same month, with the reasons and the right to complain to a supervisory authority. 🔑 The month is not a deadline for the erasure; it is a deadline for the ANSWER — and those are different obligations that a single "we will handle it" reply is often written to blur.

① Why the build made this newly sharp rather than newly true. The endpoint now schedules: the owner's rule puts a grace period between the request and the irreversible act, and ErasureDueWorker carries it out on a clock. That is the right design — but it means that on the day the subject asks, nothing has happened yet, and a reply saying only "your request has been received" leaves the Art. 12(3) duty unmet while looking like compliance. 🔴 A scheduled erasure needs a reply that names the date, because the date is the only thing that makes the schedule checkable by the person it is about.

③ What CIQRA must therefore send — four fields, and the fourth is the one that gets dropped: ⓐ what was decided (erase / partly erase / refuse); ⓑ the effective date, where it is scheduled; ⓒ what is being kept and why, naming the ground — today that includes the consent record retained under Art. 17(3)(e) (PA-0060) and the accounting records retained under Estonian law; ⓓ that they may complain to a supervisory authority, which Art. 12(4) requires whenever CIQRA does not fully act.

🟢 BUILT — re-measured 2026-08-10T18:46Z, tree 3dbefdf56 (commit d8e501e37)

The previous measurement here read "nothing in the erasure path sends the subject anything", and it was true when taken. It stopped being true a few hours later. 🔑 A measurement is a claim about a moment, and a document that keeps it without its moment turns a finding into a falsehood on somebody else's schedule — the fix is the stamp, not more caution.

TellTheSubjectAsync (Ciqra.Api/Operator/OperatorDsarEndpoints.cs:252) now fires on all three arms, and SubjectNotifiedAt is stamped only after the dispatch returns, so the column records an act and not an intention. Against the four fields above:

fieldbuilt
what was decided✅ three templates — erasure_scheduled / erasure_refused / erasure_done
the effective date{date} on the scheduled arm
what is kept and why{basis} on the refusal; the completion names invoices and order records
the right to complain to a supervisory authorityon the refusal only

🔑 ⓓ is right BECAUSE it is only on one of the three, and my field list was the blunter instrument. The field above says the right must appear "whenever CIQRA does not fully act" — which is Art. 12(4). Scheduling and completing are acting, so Art. 12(3) governs them and the complaint sentence is not owed there. A build that had put it on all three would have looked more compliant and been less accurate. ⚠️ Recorded because the next reader will see one of three and assume two are missing.

And the extension question, answered so nobody reopens it: Art. 12(3)'s two-month extension never arises here. The month runs on the answer, and the answer is sent in the same request; a scheduled date falling outside the month is not a late reply, it is a reply that says a later date.

🔴 But the reply the scheduled email asks for has nowhere to land

The erasure_scheduled body (email-strings-en.json:222) tells the subject: "If you did not make this request, or you have changed your mind, reply to this message before that date and we will stop it." Three measured facts, same tree, same stamp: ① TellTheSubjectAsync calls SendAsync without the replyTo argument (:281), so no Reply-To header is set and the reply returns to the platform sender address — the one the transport's own note (PlatformEmailSender.cs:25) describes as an unattended mailbox. ② The message carries no reference: the template's only arguments are date and basis (OperatorDsarEndpoints.cs:269). ③ The cancellation built by the same commit is POST /dsar/requests/{entryId}/cancel, keyed by a GUID the subject was never given and gated behind the operator manage policy.

⛔ So the one action the message invites cannot be performed by the person invited to perform it, and 🔑 this holds whether or not anyone reads that mailbox — a human receiving "please stop it" with no reference has been handed a puzzle, not an instruction. ⚠️ And the stakes are asymmetric: the act the reply is meant to stop is irreversible, and the cancellation is honest only while it is still reachable — after the sweep runs there is nothing to cancel. The promise is load-bearing for exactly the ten days in which it does not work.

Fix — any one of three, in ascending cost: name a monitored address in the body; pass replyTo (the parameter exists and is already carried through, PA-0398 ②); or give the subject a signed self-service cancel link, which is the only one that closes it without a human in the path.

🔴 This is not a one-off — see §8.3b of the Merchant Agreement. The same shape appears in the notice-and-action texts, which promise an internal complaint channel that does not exist. Two instances is a class — outbound text inventing an inbound channel — and the class is written up in the queue awaiting a number from the coordinator; it is deliberately not numbered here, because a number I allocate to my own finding is not a register entry. Build is P2's; the obligation is stated here so it has a home that is not a commit message.

🔑 So the defect is not "no mechanism". It is two errors pointing in opposite directions: the periods A.2 declares are mostly unenforced, and the periods actually enforced are mostly undeclared. A reader of A.2 therefore gets no reliable statement in either direction, which is worse than a plain gap — a gap at least reads as a gap. PA-0017.

What does exist:

PromiseMechanismStatus
Backups — 30 days rollingBackupSchedule.cs:22,27,29,31RetentionDays default 30, min 1, max 365, enforced by BackupScheduleActivator and clamped in BackupScheduleService.cs:43-46mechanised
Erasure on data-subject requestOperatorDsarErasureService.cs, ErasureRetentionPolicy.cs, ErasureResidueScanner.cs, register migration 20260729182419_AddOperatorDsarRegister — classifies each table as Deleted / Anonymised / Retained / RetainedAnonymised and reports categories nobody classified rather than silently leaving data behindmechanised (request-driven)
Analytics events — 400 days raw / 1,100 days rollupRetentionService.cs hard-deletes raw events past EventIngestionOptions.RawRetentionDays (400) and rollups past RollupRetentionDays (1,100) — EventIngestionOptions.cs:33,36mechanised — 🔴 but the period is not declared in A.2
Closed-tenant data — promise says +90 daysTenantPurgeWorker.cs + TenantPurgeOptions.cs:17,20 — the mechanism exists, its period is 730 days, and AutoPurgeEnabled defaults to false⚠️ mechanism exists, disagrees with the promise, and is off
Audit / session trail — 365 daysAuditRetentionWorker deletes audit rows past AuditRetention.Window (365 days, AuditRetention.cs:29); the trail stores an IP address, so an unbounded trail would be a retention decision nobody mademechanised — 🔴 period is a technical default awaiting the owner's number
MCP confirmation previews — 1 day past expiryMcpConfirmationSweeper.cs hourly-deletes mcp_confirmation_challenges past McpConfirmationRetention.Grace (1 day). The preview column is up to 8 KB of server-computed text naming the values a merchant is approving, so a connector tool acting on an order or a customer can quote a data subject in it. 🔴 The table carries none of the link columns subject-access and erasure discover categories by, so this worker is the ONLY thing that removes that text — there is no request-driven path behind itmechanised — 🔴 the period is not declared in A.2, the same gap as the analytics row above
Every remaining time-based row in A.2 — 7 years, 13 months, 12–24 months, 24 months(none found)no mechanism

🔴 CIQRA's determination (revised 2026-08-10). Most of the time-based periods in A.2 are not enforced by any automated job; this document earlier claimed retention was "enforced by process and, where feasible, automated jobs", which read as a description of a control that did not exist, and that claim stays withdrawn. Two further findings now sit beside it:

  • An enforced period that is not declared is a transparency defect, not a documentation tidy-up. GDPR Art. 13(2)(a) requires the storage period to be given to the data subject. Analytics events carrying pseudonymous identifiers are deleted at 400 days by a job that runs today, and no reader of this Policy could learn that. The number is now in the table above and belongs in A.2 at the next revision.
  • TenantPurgeWorker is the sharper case: the mechanism disagrees with the promise, and then does not run. A.2 commits to deleting or anonymising Merchant account data 90 days after closure. The purge worker's default period is 730 days — eight times longer — and AutoPurgeEnabled is false by default, so neither figure is what happens. ⚠️ Turning the worker on without changing the period would not satisfy the promise; it would enforce a different one. The two must be fixed together or the fix reads as done and is not.

This also constrains Art. 5(1)(e) (storage limitation) and the DPA §7 return/deletion obligation: both currently rest on manual process.

A.0b The four periods that looked like a clash — three of them never were

Four numbers sat next to each other in different records — 365 days, 10 days, 183 days and 6 months — and read together they looked like a platform with several retention regimes. They are not. Three of them answer different questions, and only one pair collides.

NumberWhereWhat question it answersKind
365 daysAuditRetention.cs:29How long the audit / session trail (which stores an IP) is kepta retention period
10 daysErasureTiming.csGracePeriodHow long CIQRA waits between an erasure request and acting on it, so a mistaken or hijacked request can be caught before an irreversible operationa procedural window — retains nothing
183 daysErasureTiming.csRecentOrderWindowWhether a recent order withholds redaction, because returns, chargebacks and warranty claims reach back into that perioda claims window — a condition, not a period of storage
6 monthsthe PA-0009 decision as recorded in the 1.0 scope gateThe same question as the row above

🔑 Only the last pair is a real conflict, and it is a conflict of EXPRESSION, not of intent. "Six months" and "183 days" are the same decision written two ways, and they do not land on the same day: six months from 15 January is 15 July, 183 days is 17 July. The gap is two days and it never matters to a merchant — but it matters to a boundary test, and a rule about dates that cannot be tested at its edges is the shape this platform has paid for before.

③ CIQRA's resolution: the day count is authoritative. The redaction rule is a pure function whose boundaries are asserted exactly on the day before and the day after the threshold; a calendar term cannot be asserted that way, because its length depends on which month it starts in. 183 days is the expression of PA-0009's "half a year" (365 ÷ 2, rounded up), the owner's decision is unchanged, and any document or entry still saying "6 months" for this rule should be read as saying 183 days.

⚠️ And the other three must stop being listed together. A grace period is not a retention period; a claims window is not a retention period. Presenting them in one column invited exactly the reading that there were four competing regimes, which delayed a real decision — the erasure binding was withdrawn on the strength of an apparent clash that mostly did not exist. 🔑 Numbers that answer different questions are not reconciled by choosing between them; they are reconciled by writing down which question each one answers.

🔴 What is still open here, and it is the pair D found: RetentionService deletes analytics events at 400 days and that period appears in no published text (Art. 13(2)(a)), while TenantPurgeWorker is set to 730 days against a promise of +90 days and is disabled by default. Those two are genuine mismatches between promise and mechanism, unlike the four above. PA-0017.

🔴 A note on this section's own numbers. The worker census in A.0 has been wrong repeatedly — first "five workers, none deleting", then "twenty workers, three deleting", then 21 and four when AuditRetentionWorker landed in between. The live figure is the one A.0 declares — 25 and six as of 2026-09-03 — and not the numbers in this sentence, which are kept only as the history of how often it drifted. 🔑 A census written into a document is a snapshot, and a snapshot rots exactly like a hand-written countdown does — including a snapshot that sits two paragraphs under a corrected one and quietly contradicts it. The count is therefore now declared rather than merely stated: tools/legal-gate/legalgate.py counts the background workers in the tree and fails if the number here disagrees. ⚠️ Declared limit: the tool counts workers, not deleters — which of them delete on a clock cannot be decided by a pattern (two of the four delete through a helper class), so that column stays a human classification. The count is the tripwire: a worker cannot be added without someone being made to look at this table.

A.1 Principles

We retain personal data only as long as necessary for the purpose collected or as required by law, then delete or irreversibly anonymise it. Where CIQRA is processor (Storefront Customer data), the Merchant's instructions and the DPA govern; the periods below are CIQRA's defaults absent a conflicting lawful Merchant instruction.

Enforcement, stated accurately (revised 2026-09-04, PA-0017): every row of the matrix below is now bound to a named job or carries a written exemption, and the binding is held once in Ciqra.Api.Retention.PlatformRetention. PlatformRetentionPromiseTests reads the §A.2 table out of this file at test time and fails when a category here has no entry there, so the two cannot drift: a promise added below without a job or an exemption turns the suite red. ⚠️ The earlier sentence — "all other periods are enforced by manual process" — is superseded, not merely softened.

A.2 Retention matrix (declared defaults — see A.0 for enforcement status)

Data categoryRetention periodBasis / rationaleMechanism
Order, invoice, tax & accounting records7 years after the financial yearEstonian Accounting Act (legal obligation)🔒 declared exemption in PlatformRetention — a 7-year MINIMUM, not a deletion clock. Discharged by TenantPurgeWorker (whole merchant, +90d after closure) and by the DSAR erasure, which removes the PERSON and keeps the TRANSACTION. An unattended age sweep would cascade across orders/order_lines/invoices/invoice_lines/ledger_entries with no way back, on a horizon the law states in financial years
Merchant account & profile dataLife of account + 90 days after closure, then delete/anonymiseContract; wind-down; disputesmechanisedTenantPurgeWorker; TenantPurgeOptions.RetentionDays corrected 730 → 90 on 2026-09-04 so the mechanism enforces THIS promise and not a different one
KYC/KYB & verification recordsSee A.2.1 belowLegal obligation🔒 declared exemption in PlatformRetention — CIQRA holds no identity documents (A.2.1); the 5-year figure is withdrawn, so there is no period left to enforce
Payment & chargeback records13 months+ (extended for open disputes)Dispute/chargeback windows; legal claimsmechanisedPlatformRetentionWorker at 396 days over billing_webhook_events, processed_native_callbacks, processed_vault_webhook_events, bank_vpos_sessions
Customer/order personal data (as processor)Per Merchant instruction; default aligned to accounting (7 yrs for tx records)DPA; controller instruction🔒 declared exemption in PlatformRetention — CIQRA is PROCESSOR and the period is the controller's instruction; a platform clock would override it, which is the one thing a processor may not do
Support tickets & correspondence12–24 monthsLegitimate interests; service qualitymechanisedPlatformRetentionWorker at 730 days (the band's outer bound) over support_tickets (measured from last_message_at, not the opening message), chat_messages, form_submissions
Application & security logsApplication 12 months; security/audit up to 24 monthsSecurity; legitimate interestsmechanisedAuditRetentionWorker (audit_events, 365d, inside the 24-month allowance) + PlatformRetentionWorker at 365 days over legacy_application_logs (from occurred_at), session_devices, webhook_deliveries, email_deliveries, sms_logs, feed_import_run_rows, product_index_changes, analytics_delivery_outbox (terminal only)
Marketing consent & preferencesUntil consent withdrawal / objection, + record of consent for evidenceConsent; accountability🔒 declared exemption — NO CLOCK IS THE CORRECT MECHANISM. The period is until withdrawal, and the record is kept AFTER it as evidence: art. 7(1) puts the burden of demonstrating consent on the controller, so an expiry job would destroy the proof. newsletter_subscribers is additionally declared retained under art. 17(3)(e) so a DSAR erasure cannot delete it either (PA-0060)
Marketing engagement data24 months after last interactionLegitimate interestsmechanisedPlatformRetentionWorker at 730 days over cart_recovery_sends, back_in_stock_requests
Cookies / tracking identifiersPer Cookie Policy durations; consent-basedConsent✅ browser-enforced lifetimes
AI feature inputs/outputsSee A.2.2 belowContract; data minimisation⚠️ see A.2.2
Backups30 days rolling, then overwrittenResilienceBackupSchedule.cs:27 (default 30)
Deleted-account residual (legal hold)Only data under a specific legal obligation/holdLegal obligationErasureRetentionPolicy.cs classifies retained-vs-erased, and from 2026-09-04 names the article each table is retained under rather than reporting every one of them as art. 17(3)(b)

A.2.1 KYC/KYB retention.

CIQRA's determination: CIQRA does not itself perform KYC/KYB. Onboarding identity verification is carried out by Stripe as part of Connect account creation (measured: StripeConnectService.cs:60-72 creates an express account with CardPayments and Transfers capabilities requested; Stripe collects and holds the verification data). CIQRA therefore holds Connect account references and status, not the underlying identity documents. The "typically 5 years after the relationship ends" figure in an earlier internal version was a generic AML default that was never matched to a CIQRA obligation, and CIQRA has not determined whether it is itself an obliged entity under Estonian AML law (RahaPTS) or whether the obligation sits wholly with Stripe. The 5-year figure is withdrawn pending that determination; the records CIQRA does hold fall under the account-data row above. See also Payment Terms §1.

A.2.2 AI feature inputs/outputs.

CIQRA's determination: an earlier internal version stated flatly that AI inputs/outputs are subject to zero retention at the provider. That is a contractual claim about four third parties, and CIQRA has not verified that an executed zero-retention term exists for any of them — see AI Terms §2.3 and Subprocessor List §4. Two of the four providers actually in use (Voyage AI, fal.ai) were absent from the subprocessor list entirely until 2026-08-09. Published vendor policy indicates limited retention for abuse monitoring at OpenAI (~30 days) rather than zero by default. This row should read "per executed provider terms — currently unverified", not "zero-retention".

CIQRA's own operational metadata for AI calls (usage/cost accounting) is retained and is real: AiUsageMeter, AiResponseCache (in-memory, 1-minute TTL, AiResponseCache.cs:29).

A.3 Deletion & anonymisation

  • On account closure/termination: export window (ToS §13.3), then deletion/anonymisation per this matrix, minus legally-required retention (segregated, access-restricted) — manual, per A.0.

  • Backups containing deleted data are purged on the rolling cycle; restores re-apply deletions.

    CIQRA's determination: "restores re-apply deletions" describes an intended operational step. No mechanism was found that re-applies an erasure log after a restore, and the PITR drill recorded elsewhere in the programme did not test it. Treat as procedure, not control.

  • Erasure that survives inspection. Where a request is executed, the erasure service reports what it did and flags any category or identity column it could not classify rather than reporting success (OperatorDsarErasureService.cs:80-93, ErasureResidueScanner.cs:59). This is the one place in the retention chain where an incomplete result is visible rather than silent.

  • KVKK — live row. Türkiye is a launch market (owner resolution 7, 2026-08-13) and the TR overlay §6.2 is in force, so KVKK's periodic destruction / anonymisation duty and the kişisel veri saklama ve imha politikası apply to personal data of data subjects in Türkiye. Doc 09 §6.2 aligns the TR items to this matrix rather than opening a second one, so the periods above are the periods that govern.

    CIQRA's determination: the obligation is live; the artefacts are partly built. The written saklama ve imha politikası has not been produced — that half is unchanged and remains open under TR-02. The VERBİS question is no longer undetermined: on 2026-08-17 CIQRA determined that the registration obligation and the TR representative obligation are to be treated as having arisen, and the reasoning, the facts it rests on and its review horizon are held once in the KVKK operability record §1 rather than restated here. 🔴 The registration itself has not been made and no representative has been appointed — that is the one step outside this repository, owner CIQRA OÜ (record §1.2). This row states the duty and does not claim it is discharged. Until 2026-08-13 this row was carried as deferred on the footing that TR was not a launch market; that deferral is retired, which is not the same as satisfied.


Part B — Personal-Data Breach Notification (GDPR Art. 33/34)

B.1 Definition & scope

A personal-data breach is a breach of security leading to accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to, personal data. This procedure covers breaches affecting data for which CIQRA is controller (we notify the supervisory authority/data subjects) and data for which CIQRA is processor (we notify the Merchant/Controller — see B.5).

B.2 The 72-hour clock

When CIQRA (as controller) becomes aware of a breach likely to result in a risk to individuals' rights and freedoms, it notifies the competent supervisory authority — the Estonian Andmekaitse Inspektsioonwithout undue delay and, where feasible, within 72 hours of becoming aware. If notification is later than 72 hours, reasons for the delay are given. If unlikely to result in a risk, notification may not be required (documented in the internal register).

CIQRA's determination: CIQRA identifies Andmekaitse Inspektsioon as its lead supervisory authority on the basis of its Estonian establishment (main establishment = registered office, Tallinn). Because CIQRA serves globally and processes data of individuals in other Member States, a one-stop-shop analysis under Art. 56 has not been performed, and cross-border processing could engage concerned authorities. This has not been confirmed with the authority.

B.3 Incident response steps

  1. Detect & report — anyone aware of a suspected breach reports immediately to security@ciqra.com (internal incident channel).
  2. Triage & contain — the incident lead assesses scope, contains, and preserves evidence; the 72-hour clock starts at "awareness".
  3. Assess risk — data categories, volume, data subjects, likelihood/severity of harm, whether data was encrypted/pseudonymised.
  4. Notify authority (if risk) within 72h — nature, categories/approx numbers, likely consequences, measures taken/proposed, privacy-contact details (staged notification allowed).
  5. Notify data subjects (if high risk) — without undue delay, in clear language, with mitigation advice (Art. 34), unless an exemption applies.
  6. Notify Merchants/Controllers & partners — see B.5; notify Stripe/insurers/authorities as required.
  7. Record — log every breach in the internal breach register, regardless of notifiability (Art. 33(5)).
  8. Remediate & learn — root-cause fix, preventive measures, post-incident review.

CIQRA's determination: steps 1–8 describe a procedure. This reconciliation did not verify that the internal incident-response runbook, the on-call rota and the breach register referenced in step 7 and B.4 exist as artefacts. An Art. 33(5) register is a documentation obligation that applies from day one and is independent of whether a breach has occurred. Confirm the register exists before launch.

B.4 Roles

  • Incident lead / privacy contact: privacy@ciqra.com coordinates; the incident lead is the on-call member of the management/engineering team designated in the internal incident-response runbook (roles: Incident Lead, Engineering/Forensics, Legal/Counsel liaison, Comms).
  • Engineering (containment/forensics), Legal/counsel (notifiability, regulator liaison), Comms (external messaging).

Note on the DPO. 🔒 Resolved 2026-08-10: no DPO is appointed, and the Art. 37 assessment behind that decision is written out in Privacy Policy §1.1a. The two internal records that previously disagreed are both superseded by it. ⚠️ The assessment is bounded: Art. 37(1)(b) is engaged on nature and excluded only on scale, so it is re-run before a second storefront goes live. Breach notifications under Art. 33(3)(b) must name a contact point regardless, and privacy@ciqra.com is that point whether or not a DPO is ever appointed.

B.5 CIQRA as processor

Where the breach affects data CIQRA processes for a Merchant (Controller), CIQRA notifies that Merchant without undue delay and within 72 hours of awareness (per the DPA §8), providing the information the Merchant needs for its own Art. 33/34 duties, and assists the Merchant's response. The Merchant decides on authority/data-subject notification for its data (unless otherwise agreed).

Note on the standard: Art. 33(2) requires a processor to notify the controller without undue delay and sets no 72-hour limit for that leg. CIQRA has chosen to commit to 72 hours contractually. That is a commitment CIQRA is taking on voluntarily, and — per A.0 — it depends on detection capability, which this reconciliation did not measure.

B.6 Cross-regime

  • TR / KVKK — live. Türkiye is a launch market (owner resolution 7, 2026-08-13) and the TR overlay §6.3 is in force: a personal-data breach affecting data subjects in Türkiye is notified to the Kişisel Verileri Koruma Kurumu and to the affected data subjects in addition to the GDPR Art. 33/34 legs above, in the manner and within the periods the Kurul's decisions require.

    CIQRA's determination: the duty is live and the mechanism is partly established. The two regimes' steps, actors and clocks are now written as one sequence in the KVKK operability record §2, held there rather than restated here so the GDPR and KVKK legs cannot drift apart in two documents. 🔴 The applicable KVKK notification period and the channel used to reach the Kurul remain outstanding — record §2.2 keeps that as a named gap and, rather than inventing a period, runs the sequence against the GDPR 72-hour clock with the KVKK leg prepared inside the same window, which is the stricter of the two directions. What would close it is a row in the Legal Basis Register §7 citing the Kurul decision that fixes the period, with a source and a retrieval date. Open item, closing under TR-02. Carried as deferred until 2026-08-13; that deferral is retired, not satisfied.

  • Payment/card data — measured. CIQRA's SAQ A posture is measured-consistent: storefront card fields are Stripe Elements iframes and no implemented code path receives a raw PAN (see Subprocessor List §3 for the full measurement and its one named condition). Any suspected payment-data incident is escalated to Stripe and handled per card-scheme/PCI rules.

B.7 Testing

This procedure is reviewed and tested at least annually; contact lists and the register are kept current.

CIQRA's determination (revised 2026-08-18). The earlier finding — "no evidence of a completed breach-response test was located" — is superseded in part, and only in part. A rehearsal has now been walked and recorded: KIL-01, 2026-08-18, logged in the KVKK operability record §3.1 with its scenario, environment, per-step wall-clock timings, five findings and their dispositions.

⚠️ What it was, stated so this row is not read for more than it says. It walked the mechanical steps — scoping the affected records, counting the data subjects, and identifying the Türkiye subset — against an ephemeral test database, and it was walked by one agent with no human present. It did not exercise the on-call rota, did not draft or send a notification to anyone, and did not verify that the Art. 33(5) register in B.3 exists. Record §3.3 lists what it could not prove, in its own words.

🔴 Its principal finding is unresolved and is recorded rather than closed: the number of affected data subjects in Türkiye has four different answers — 1, 2, 2 or 3 out of a population of 4 — because the platform stores the order's delivery jurisdiction, the order's billing jurisdiction and the customer's address-book country, and nothing says which of them decides. Under a 72-hour clock that is the step at which the clock stops. Record §3.2 F-1; owner: product.

So "tested at least annually" is no longer only a forward commitment — it has one dated exercise behind it, its next due date is enforced by legalgate's KVKK check against the interval the record states, and the exercise's limits are written down beside it.


Provision register (this document)

ProvisionBasisWhere
KYC/KYB 5 years(A) measured Stripe performs KYC + (B) 5-yr figure withdrawn, obliged-entity status undeterminedA.2.1
KVKK destruction policy(B) live under overlay 09 §6.2 — policy not written; open item TR-02. VERBİS trigger determined 2026-08-17 (obligation treated as arisen — record §1); the registration itself is outstandingA.3
72h authority notification(B) lead-authority / one-stop-shop unconfirmedB.2
KVKK breach duties(B) live under overlay 09 §6.3 — period, channel and rehearsal unestablished; open item TR-02B.6
payment incident escalation(A) measured SAQ A consistent, one named conditionB.6
(not previously flagged)🔴 (A) measured — no retention job exists; "enforced by automated jobs" withdrawnA.0
(not previously flagged)(A) measured — backup retention and DSAR erasure ARE mechanisedA.0
(not previously flagged)(B) AI zero-retention unverified; row rewrittenA.2.2
(not previously flagged)(B) restore-re-applies-deletions is procedure, not controlA.3
(not previously flagged)(B) Art. 33(5) breach register existence unverifiedB.3

Composition: 4 measured facts · 8 declared positions · 2 withdrawn claims (automated retention enforcement; AI zero-retention).

Declared positions moved 6 → 8 on 2026-08-13: the two KVKK rows were previously carried as ⏸️ deferred, which counted as neither. A deferred row is not a third category — it is a position nobody wrote down. Both are now declared, both are open under TR-02, and the count says so rather than the table quietly disagreeing with its own total.

End of Retention & Breach procedure . See: Privacy Policy · DPA · Subprocessor List.