Origin: Viability report ops/analysis/2026-06-10-voidpay-viability/report.md §9 (T1–T3). Owner: Kai (CTO). Co-review: Shade (CISO).
Decoupled from: spec 092-distribution-experiment (Phase 0 lifted out per Ignat 2026-06-10 — trust-fixes have privacy/security nature + own owner, не живут внутри distribution-спека).
Constitution: II (Privacy-first — never PII / financial data on servers).
Why a track, not a spec
Three independent privacy-hygiene fixes, all <1 day combined. For a product whose brand = privacy, these are "один внешний аудит до конфуза" (report §9). T4 (publish design-spec) lives on the void-layer track — see void-layer/codec issue, not here.
▌ Distribution gate
092 distribution start is gated on T1 + T2 deployed to prod. T3 is do-now-but-de-gateable (residual, not externally exposed). When T1+T2 are merged & live → this issue unblocks 092.
T1 — blocklist copy: present-claim → reserved-right [gating]
Problem: privacy/terms describe a blocklist as existing; zero implementation in code. Active misrepresentation to the privacy audience.
Decision (Kai): do NOT build the blocklist now (no abuse signal — 3 payments all-time; building = days, over-engineering pre-PMF). Rewrite tense present→reserved-right. Keeps legal cover, stops the false claim. Real implementation → separate backlog issue, triggered by first real abuse.
Sites (4):
src/app/privacy/content.ts:149 — "we maintain a public blocklist" → "we may maintain / reserve the right to maintain"
src/app/privacy/content.ts:153,165 — soften present-tense description of structure/publicity to conditional
src/app/terms/content.ts:192 — "we reserve the right to add … to our public blocklist" (already reserved-right — verify wording, likely OK)
src/app/terms/content.ts:243 — verify reserved-right framing holds
Owner: Spark (final wording → copy-editing Sweep-2 voice-gate) → Atlas applies. Co-review Shade: does reserved-right language retain legal abuse-cover without a present claim.
Effort: ~2–3h.
T2 — og-params leak into Umami url_query [gating]
Problem: ?og=INV_amounts_names written to Umami url_query (93 events, last 2026-06-06), against "collects no personal or financial data". UmamiScript.tsx:28 has data-exclude-hash="true" but NOT data-exclude-search.
Fix: add data-exclude-search="true" to src/features/analytics/ui/UmamiScript.tsx Script tag. Update the comment block (line 13–18) to document both guards.
⚠ Scope boundary: this closes the Umami sink only. The Vercel access-logs sink for the same og-params is a separate, heavier fix tracked in #211 (P2, not gating). Closing T2 does NOT close #211 — do not mark og-leak fully resolved.
⚠ Phase-1c coupling (for Spark/092): blanket data-exclude-search strips ALL query strings from Umami. 092 Phase 1c attribution must therefore use custom-event data (track('invoice-create', { ch })), NOT a URL ?ch= query — the query would be stripped here. Privacy-positive forcing function; design 1c accordingly.
Owner: Atlas (Kai brief — pure code, my lane, no voice-gate). Snapshot test on UmamiScript.
Effort: minutes.
T3 — purge 37 residual PII rows in Umami [do-now, de-gateable]
Problem: 37 rows with full invoice-hash PII, window 2026-02-05→03-28. Leak already stopped (data-exclude-hash since day-0). Residual data in private analytics DB (vp-metrics), not externally exposed.
Fix (mandatory order, no shortcuts):
SELECT count(*) on exact predicate (url_query/url_path LIKE pattern + date window) — eyeball count vs 37
- snapshot/backup (
CREATE TABLE _bak AS SELECT … or Supabase PITR)
DELETE
Risk: only risk = wrong WHERE deletes good rows. Shade reviews the predicate before DELETE.
Owner: Ignat runs SQL via supabase db query --linked (ref: memory reference_supabase_umami). Co-review Shade (predicate).
Effort: minutes + backup discipline.
De-gateable: not externally visible → does not block 092 start by itself; bundled here because it's cheap and closes the loop.
Sequencing
- T2 — 1 line, deploy first, stops live leak. Fix 1c coupling note → Spark.
- T1 — parallel; Spark wording → Shade legal-check → Atlas applies.
- T3 — parallel; SELECT-verify → snapshot → DELETE (Shade predicate review).
- Gate-check: T1+T2 merged & live → comment here → 092 unblocked.
Acceptance
Co-review
- shade-ciso: T1 legal-cover wording, T3 SQL predicate
- spark-cmo: T1 final copy (voice-gate), T2 Phase-1c attribution design
Origin: Viability report
ops/analysis/2026-06-10-voidpay-viability/report.md§9 (T1–T3). Owner: Kai (CTO). Co-review: Shade (CISO).Decoupled from: spec 092-distribution-experiment (Phase 0 lifted out per Ignat 2026-06-10 — trust-fixes have privacy/security nature + own owner, не живут внутри distribution-спека).
Constitution: II (Privacy-first — never PII / financial data on servers).
Why a track, not a spec
Three independent privacy-hygiene fixes, all <1 day combined. For a product whose brand = privacy, these are "один внешний аудит до конфуза" (report §9). T4 (publish design-spec) lives on the void-layer track — see void-layer/codec issue, not here.
▌ Distribution gate
092 distribution start is gated on T1 + T2 deployed to prod. T3 is do-now-but-de-gateable (residual, not externally exposed). When T1+T2 are merged & live → this issue unblocks 092.
T1 — blocklist copy: present-claim → reserved-right
[gating]Problem: privacy/terms describe a blocklist as existing; zero implementation in code. Active misrepresentation to the privacy audience.
Decision (Kai): do NOT build the blocklist now (no abuse signal — 3 payments all-time; building = days, over-engineering pre-PMF). Rewrite tense present→reserved-right. Keeps legal cover, stops the false claim. Real implementation → separate backlog issue, triggered by first real abuse.
Sites (4):
src/app/privacy/content.ts:149— "we maintain a public blocklist" → "we may maintain / reserve the right to maintain"src/app/privacy/content.ts:153,165— soften present-tense description of structure/publicity to conditionalsrc/app/terms/content.ts:192— "we reserve the right to add … to our public blocklist" (already reserved-right — verify wording, likely OK)src/app/terms/content.ts:243— verify reserved-right framing holdsOwner: Spark (final wording →
copy-editingSweep-2 voice-gate) → Atlas applies. Co-review Shade: does reserved-right language retain legal abuse-cover without a present claim.Effort: ~2–3h.
T2 — og-params leak into Umami
url_query[gating]Problem:
?og=INV_amounts_nameswritten to Umamiurl_query(93 events, last 2026-06-06), against "collects no personal or financial data".UmamiScript.tsx:28hasdata-exclude-hash="true"but NOTdata-exclude-search.Fix: add
data-exclude-search="true"tosrc/features/analytics/ui/UmamiScript.tsxScript tag. Update the comment block (line 13–18) to document both guards.⚠ Scope boundary: this closes the Umami sink only. The Vercel access-logs sink for the same og-params is a separate, heavier fix tracked in #211 (P2, not gating). Closing T2 does NOT close #211 — do not mark og-leak fully resolved.
⚠ Phase-1c coupling (for Spark/092): blanket
data-exclude-searchstrips ALL query strings from Umami. 092 Phase 1c attribution must therefore use custom-event data (track('invoice-create', { ch })), NOT a URL?ch=query — the query would be stripped here. Privacy-positive forcing function; design 1c accordingly.Owner: Atlas (Kai brief — pure code, my lane, no voice-gate). Snapshot test on UmamiScript.
Effort: minutes.
T3 — purge 37 residual PII rows in Umami
[do-now, de-gateable]Problem: 37 rows with full invoice-hash PII, window 2026-02-05→03-28. Leak already stopped (
data-exclude-hashsince day-0). Residual data in private analytics DB (vp-metrics), not externally exposed.Fix (mandatory order, no shortcuts):
SELECT count(*)on exact predicate (url_query/url_path LIKEpattern + date window) — eyeball count vs 37CREATE TABLE _bak AS SELECT …or Supabase PITR)DELETERisk: only risk = wrong
WHEREdeletes good rows. Shade reviews the predicate before DELETE.Owner: Ignat runs SQL via
supabase db query --linked(ref: memoryreference_supabase_umami). Co-review Shade (predicate).Effort: minutes + backup discipline.
De-gateable: not externally visible → does not block 092 start by itself; bundled here because it's cheap and closes the loop.
Sequencing
Acceptance
data-exclude-search="true"shipped + snapshot test; [privacy] OG-param hashing/truncation — Vercel logs leak invoice metadata (Constitution II.2 hardening) #211 cross-ref noted as still-openCo-review