493 lines
20 KiB
HTML
493 lines
20 KiB
HTML
<title>Privacy Feature Survey</title>
|
|||
|
|
<style>
|
||
|
|
:root {
|
||
|
|
--paper: #FAFAF7;
|
||
|
|
--ink: #1C1C1A;
|
||
|
|
--muted: #6B6B63;
|
||
|
|
--accent: #2F5D50;
|
||
|
|
--accent-soft: #E4EDE9;
|
||
|
|
--good: #3F7A5C;
|
||
|
|
--good-soft: #E6F0EA;
|
||
|
|
--warn: #B8863B;
|
||
|
|
--warn-soft: #F5ECDC;
|
||
|
|
--miss: #A14B3B;
|
||
|
|
--miss-soft: #F5E5E0;
|
||
|
|
--hairline: #E4E2DB;
|
||
|
|
--card: #FFFFFF;
|
||
|
|
}
|
||
|
|
|
||
|
|
:root:not([data-theme="light"]) {
|
||
|
|
@media (prefers-color-scheme: dark) {
|
||
|
|
--paper: #17181A;
|
||
|
|
--ink: #EDEBE4;
|
||
|
|
--muted: #9B9A90;
|
||
|
|
--accent: #7FBFA8;
|
||
|
|
--accent-soft: #1E2C27;
|
||
|
|
--good: #6FBF97;
|
||
|
|
--good-soft: #1B2A22;
|
||
|
|
--warn: #D9A85C;
|
||
|
|
--warn-soft: #2C2418;
|
||
|
|
--miss: #D97C68;
|
||
|
|
--miss-soft: #2E1E1A;
|
||
|
|
--hairline: #2C2D2E;
|
||
|
|
--card: #1E1F21;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
:root[data-theme="dark"] {
|
||
|
|
--paper: #17181A;
|
||
|
|
--ink: #EDEBE4;
|
||
|
|
--muted: #9B9A90;
|
||
|
|
--accent: #7FBFA8;
|
||
|
|
--accent-soft: #1E2C27;
|
||
|
|
--good: #6FBF97;
|
||
|
|
--good-soft: #1B2A22;
|
||
|
|
--warn: #D9A85C;
|
||
|
|
--warn-soft: #2C2418;
|
||
|
|
--miss: #D97C68;
|
||
|
|
--miss-soft: #2E1E1A;
|
||
|
|
--hairline: #2C2D2E;
|
||
|
|
--card: #1E1F21;
|
||
|
|
}
|
||
|
|
|
||
|
|
* { box-sizing: border-box; }
|
||
|
|
|
||
|
|
body {
|
||
|
|
background: var(--paper);
|
||
|
|
color: var(--ink);
|
||
|
|
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||
|
|
font-size: 15.5px;
|
||
|
|
line-height: 1.55;
|
||
|
|
margin: 0;
|
||
|
|
padding: 4.5rem 1.5rem 6rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.wrap {
|
||
|
|
max-width: 780px;
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
header.page {
|
||
|
|
margin-bottom: 3.25rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.eyebrow {
|
||
|
|
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||
|
|
font-size: 0.72rem;
|
||
|
|
letter-spacing: 0.12em;
|
||
|
|
text-transform: uppercase;
|
||
|
|
color: var(--accent);
|
||
|
|
margin-bottom: 0.9rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
font-family: "Fraunces", Georgia, serif;
|
||
|
|
font-weight: 560;
|
||
|
|
font-size: clamp(2.1rem, 4.5vw, 2.65rem);
|
||
|
|
line-height: 1.08;
|
||
|
|
letter-spacing: -0.01em;
|
||
|
|
margin: 0 0 0.9rem;
|
||
|
|
text-wrap: balance;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dek {
|
||
|
|
color: var(--muted);
|
||
|
|
max-width: 60ch;
|
||
|
|
font-size: 1.02rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dek strong {
|
||
|
|
color: var(--ink);
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.legend {
|
||
|
|
display: flex;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 0.6rem;
|
||
|
|
margin-top: 1.6rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chip {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 0.4rem;
|
||
|
|
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||
|
|
font-size: 0.72rem;
|
||
|
|
letter-spacing: 0.04em;
|
||
|
|
padding: 0.28rem 0.6rem;
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.chip.have { background: var(--good-soft); color: var(--good); }
|
||
|
|
.chip.partial { background: var(--warn-soft); color: var(--warn); }
|
||
|
|
.chip.missing { background: var(--miss-soft); color: var(--miss); }
|
||
|
|
|
||
|
|
.branch-note {
|
||
|
|
margin-top: 1.4rem;
|
||
|
|
padding: 0.85rem 1rem;
|
||
|
|
background: var(--accent-soft);
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 0.86rem;
|
||
|
|
color: var(--ink);
|
||
|
|
max-width: 66ch;
|
||
|
|
}
|
||
|
|
|
||
|
|
.branch-note code {
|
||
|
|
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||
|
|
font-size: 0.85em;
|
||
|
|
color: var(--accent);
|
||
|
|
}
|
||
|
|
|
||
|
|
section.category {
|
||
|
|
margin-top: 3rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.cat-head {
|
||
|
|
display: flex;
|
||
|
|
align-items: baseline;
|
||
|
|
gap: 0.85rem;
|
||
|
|
border-bottom: 1px solid var(--hairline);
|
||
|
|
padding-bottom: 0.7rem;
|
||
|
|
margin-bottom: 1.1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.cat-num {
|
||
|
|
font-family: "Fraunces", Georgia, serif;
|
||
|
|
font-size: 1.05rem;
|
||
|
|
color: var(--accent);
|
||
|
|
font-variant-numeric: tabular-nums;
|
||
|
|
min-width: 1.6rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.cat-head h2 {
|
||
|
|
font-family: "Fraunces", Georgia, serif;
|
||
|
|
font-weight: 500;
|
||
|
|
font-size: 1.28rem;
|
||
|
|
margin: 0;
|
||
|
|
letter-spacing: -0.005em;
|
||
|
|
}
|
||
|
|
|
||
|
|
.cat-note {
|
||
|
|
color: var(--muted);
|
||
|
|
font-size: 0.86rem;
|
||
|
|
margin: 0 0 1.2rem;
|
||
|
|
max-width: 62ch;
|
||
|
|
}
|
||
|
|
|
||
|
|
.feature {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr auto;
|
||
|
|
gap: 0.3rem 1rem;
|
||
|
|
padding: 1.05rem 0;
|
||
|
|
border-bottom: 1px solid var(--hairline);
|
||
|
|
align-items: start;
|
||
|
|
}
|
||
|
|
|
||
|
|
.feature:last-child { border-bottom: none; }
|
||
|
|
|
||
|
|
.f-name {
|
||
|
|
font-weight: 600;
|
||
|
|
font-size: 0.98rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.f-status {
|
||
|
|
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||
|
|
font-size: 0.68rem;
|
||
|
|
letter-spacing: 0.06em;
|
||
|
|
text-transform: uppercase;
|
||
|
|
padding: 0.22rem 0.55rem;
|
||
|
|
border-radius: 3px;
|
||
|
|
white-space: nowrap;
|
||
|
|
height: fit-content;
|
||
|
|
}
|
||
|
|
|
||
|
|
.f-status.have { background: var(--good-soft); color: var(--good); }
|
||
|
|
.f-status.partial { background: var(--warn-soft); color: var(--warn); }
|
||
|
|
.f-status.missing { background: var(--miss-soft); color: var(--miss); }
|
||
|
|
|
||
|
|
.f-note {
|
||
|
|
grid-column: 1 / -1;
|
||
|
|
color: var(--muted);
|
||
|
|
font-size: 0.87rem;
|
||
|
|
margin-top: 0.15rem;
|
||
|
|
max-width: 66ch;
|
||
|
|
}
|
||
|
|
|
||
|
|
.f-note code {
|
||
|
|
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||
|
|
font-size: 0.82em;
|
||
|
|
background: var(--accent-soft);
|
||
|
|
color: var(--accent);
|
||
|
|
padding: 0.08em 0.35em;
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
footer.page {
|
||
|
|
margin-top: 4rem;
|
||
|
|
padding-top: 1.5rem;
|
||
|
|
border-top: 1px solid var(--hairline);
|
||
|
|
color: var(--muted);
|
||
|
|
font-size: 0.82rem;
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
gap: 1rem;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
footer.page a { color: var(--accent); }
|
||
|
|
|
||
|
|
@media (max-width: 560px) {
|
||
|
|
body { padding: 3rem 1.1rem 4rem; }
|
||
|
|
.feature { grid-template-columns: 1fr; }
|
||
|
|
.f-status { justify-self: start; }
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400..600&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
||
|
|
|
||
|
|
<div class="wrap">
|
||
|
|
|
||
|
|
<header class="page">
|
||
|
|
<div class="eyebrow">boboko / privacy & compliance · competitive survey</div>
|
||
|
|
<h1>What privacy & compliance elsewhere can do that boboko can't yet</h1>
|
||
|
|
<p class="dek">
|
||
|
|
A feature-by-feature pass across GDPR/CCPA compliance tooling used by Shopify,
|
||
|
|
WooCommerce, and dedicated consent-management platforms — sourced, not recalled
|
||
|
|
from memory — checked against <strong>master</strong> and the substantial,
|
||
|
|
unmerged <strong><code>Privacy</code> branch</strong> ("Feature: Creating Privacy
|
||
|
|
Basics") already built in this repo. For deciding what to finish and merge
|
||
|
|
next, not a build order.
|
||
|
|
</p>
|
||
|
|
<div class="legend">
|
||
|
|
<span class="chip have">● have</span>
|
||
|
|
<span class="chip partial">◐ partial</span>
|
||
|
|
<span class="chip missing">○ missing</span>
|
||
|
|
</div>
|
||
|
|
<div class="branch-note">
|
||
|
|
Most "partial" rows below are fully coded on the unmerged <code>Privacy</code>
|
||
|
|
branch (53 files, +3127/‑24 across two commits: <code>9f540cb</code>,
|
||
|
|
<code>59303cf</code>) but not on <code>master</code> — treated as partial, not
|
||
|
|
have, until it merges. <code>boboko:anonymize</code> is the one privacy-adjacent
|
||
|
|
command that already lives on <code>master</code> today.
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">01</span>
|
||
|
|
<h2>Right of access & erasure</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">GDPR Art. 15 (access) and Art. 17 (erasure) — the two rights every DSAR tool is built around.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Data export request (right of access)</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>PrivacyService::requestExportForCustomer()/requestExportForUser()</code> queue <code>ExportDataSubjectJob</code>, which gathers every registered provider's data and writes a CSV-per-provider zip via <code>WriteExportToCsvListener</code>. Not on <code>master</code>.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Data erasure request (right to be forgotten)</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>PrivacyService::requestErasureForCustomer()/requestErasureForUser()</code>, extensible via <code>config('core.privacy.providers')</code> — the same config-array-registration pattern as <code>NotificationRegistry</code>, keyed off <code>Modules\Core\Privacy\Contracts\PersonalDataProvider</code>.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Cancellable grace period before erasure</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: 30-day default (<code>core.privacy.grace_period_days</code>), reverted automatically on login via <code>CancelErasureOnLoginListener</code> — same pattern Shopify's own account-deletion flow uses. No native platform documents this as a first-party primitive; it's usually left to a third-party app.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Immediate erasure for regulator/legal requests</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>requestImmediateErasureForCustomer()/ForUser()</code>, typed to accept only <code>Staff $requestedBy</code> so a self-service path cannot reach it even by accident.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Multi-tenant erasure scoping (business account vs. individual login)</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only, and a genuinely uncommon feature: <code>PrivacyService</code> splits every operation into Customer-scope vs. User-scope, plus a sole-owner cascade (<code>CascadeCustomerErasureListener</code>) when erasing the last linked User orphans a Customer. No researched competitor product handles B2B multi-seat erasure this explicitly.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Right to rectification (self-service data correction)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No dedicated flow found on either branch — Art. 16 is generally satisfied today only incidentally, by a customer editing their own profile/address through existing account forms, not a tracked rectification request.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Dummy data anonymization for local dev</div>
|
||
|
|
<span class="f-status have">have</span>
|
||
|
|
<div class="f-note">On <code>master</code>: <code>src/Command/AnonymizeCommand.php</code> (<code>boboko:anonymize</code>) — scrubs <code>users</code>/<code>lunar_customers</code>, environment-guarded to <code>local</code> only. Distinct from GDPR erasure; the <code>Privacy</code> branch README diff explicitly flags this is <strong>not</strong> the compliance tool.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">02</span>
|
||
|
|
<h2>Anonymization, pseudonymization & retention</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">Deletion isn't the only lawful outcome — these are three different operations, often confused with each other.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Legal-retention pseudonymization (orders/invoices)</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>OrderDataProvider::eraseForCustomer()</code> clears PII fields but keeps order rows/totals/tax data intact, citing GDPR Art. 17(3)(b)'s legal-obligation exception — reports <code>ErasureOutcome::Pseudonymized</code>, not <code>Erased</code>, distinctly.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Per-provider retention policy, owned by the data's own module</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>PersonalDataProvider</code> deliberately has no central taxonomy — each provider (<code>CustomerDataProvider</code>, <code>AddressDataProvider</code>, <code>OrderDataProvider</code>, <code>CartDataProvider</code>, <code>ReviewDataProvider</code>) decides erase vs. pseudonymize vs. skip for its own table. <code>docs/privacy.md</code> flags <code>ReviewDataProvider</code>'s scope choice as needing review before relying on it.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Automatic data retention / auto-deletion after N days</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Neither branch has a scheduled sweep that erases stale data on its own — every erasure on the <code>Privacy</code> branch is triggered by an explicit request, not a retention-policy timer (e.g. "delete guest carts after 2 years," "purge OTP logs after 90 days").</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Audit trail of what was erased/exported and why</div>
|
||
|
|
<span class="f-status partial">partial</span>
|
||
|
|
<div class="f-note">On <code>Privacy</code> branch only: <code>DataErasureRequest.report</code> stores the full per-provider outcome as a snapshot (not a live lookup), specifically so the audit record stays readable after the underlying data is gone.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">03</span>
|
||
|
|
<h2>Consent & cookies</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">What a visitor is asked before tracking starts, and whether that choice is recorded anywhere.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Cookie consent banner (categorized: essential/analytics/marketing)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No code on either branch. Shopify ships a first-party <code>Customer Privacy API</code> recognizing four consent signals (analytics, marketing, preferences, sale-of-data); WooCommerce relies entirely on third-party plugins for this.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Granular marketing-consent tracking (email/SMS opt-in, per channel)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Not modeled anywhere in <code>Modules\Core</code> — no consent flag found on the <code>Customer</code>/<code>User</code> models on either branch.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Timestamped, versioned consent log (audit trail per visitor)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Standard feature of dedicated CMPs (OneTrust, Enzuzo, Consentmo) — a logged record of which policy version a visitor consented to and when. Nothing comparable exists in this codebase; the <code>Privacy</code> branch's audit trail covers erasure/export requests only, not consent events.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Google Consent Mode v2 / IAB TCF v2.3 integration</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Storefront/analytics-layer concern, not present in boboko-core at all — would live in the 3dealer storefront, not this package.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">04</span>
|
||
|
|
<h2>Policy & agreement management</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">Terms of service and privacy policy as tracked, versioned documents — not just static pages.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Terms-of-service / privacy-policy versioning</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No version-tracked policy document model on either branch — best practice researched: store version hashes or dated text alongside each acceptance record, review at least annually.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Per-user acceptance tracking (clickwrap audit trail)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No record of "which policy version did this customer accept, and when" anywhere in <code>Modules\Core</code>. Researched as a standard requirement for surviving a legal dispute or regulatory inquiry.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Re-acceptance prompt on material policy change</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Depends on the versioning row above existing first — nothing to gate a re-prompt on today.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">05</span>
|
||
|
|
<h2>Payment data & PCI-DSS scope</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">Whether cardholder data ever actually reaches boboko's own infrastructure.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Card data never touches application servers (tokenization)</div>
|
||
|
|
<span class="f-status have">have</span>
|
||
|
|
<div class="f-note">Verified from source: <code>docs/lunar.md</code> "Stripe integration" — payment flows through Lunar's Stripe driver (<code>Lunar\Stripe\Facades\Stripe</code>, <code>fetchOrCreateIntent()</code>/PaymentIntents), so PAN never lands in a boboko/Lunar database. Researched: this pattern alone can cut PCI-DSS scope by roughly 90% per industry sources.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Self-attested SAQ-A eligibility documentation</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">The technical precondition (no card data touching the server) is met, but nothing in <code>docs/</code> documents or asserts SAQ-A eligibility for a consuming app's own compliance paperwork.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">06</span>
|
||
|
|
<h2>Regional & regulatory coverage</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">Beyond GDPR — the other regimes a storefront selling outside the EU may need.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">CCPA "Do Not Sell/Share My Info" opt-out</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No opt-out flag or page found on either branch. Shopify's Customer Privacy API models this as a distinct fourth consent signal ("sale of data") alongside analytics/marketing/preferences — boboko has no equivalent signal at all yet.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Geo-targeted regulatory detection (GDPR vs. CCPA vs. LGPD banner)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Third-party CMPs (Consentmo, UniConsent) auto-detect visitor region to show the applicable banner/rights. No geo-based privacy-regime logic anywhere in this codebase.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Age verification / minor-data restrictions (COPPA-adjacent)</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No age gate or minor-specific data handling found on either branch.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section class="category">
|
||
|
|
<div class="cat-head">
|
||
|
|
<span class="cat-num">07</span>
|
||
|
|
<h2>Incident & vendor accountability</h2>
|
||
|
|
</div>
|
||
|
|
<p class="cat-note">What happens when something goes wrong, or when a third party is handling data on the shop's behalf.</p>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Data breach notification workflow</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No incident-tracking model or notification path found on either branch — GDPR Art. 33/34's 72-hour authority-notification and affected-subject-notification duties have no tooling here today.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Subprocessor / third-party vendor disclosure list</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">No subprocessor registry in code — Stripe is the one third-party data processor identifiable from <code>docs/lunar.md</code>, but nothing formally tracks or discloses it as a subprocessor.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="feature">
|
||
|
|
<div class="f-name">Data processing agreement (DPA) tracking per vendor</div>
|
||
|
|
<span class="f-status missing">missing</span>
|
||
|
|
<div class="f-note">Not applicable to application code directly, but no config or doc references a DPA registry either — purely a legal/ops artifact today, not represented in boboko-core at all.</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<footer class="page">
|
||
|
|
<span>Compiled 2026-08-28 — <code>have</code>/<code>partial</code> statuses sourced from direct reads of <code>master</code> and the unmerged <code>Privacy</code> branch (commits <code>9f540cb</code>, <code>59303cf</code>) via <code>git show</code>; competitor/regulatory claims sourced from web research, cited inline.</span>
|
||
|
|
<span>boboko-core / docs</span>
|
||
|
|
</footer>
|
||
|
|
|
||
|
|
</div>
|