{"breach":{"notes":"Art 33 GDPR \u2014 Processor notifies Controller without undue delay (no hard SLA imposed by this DPA). The Customer (Controller) has an independent 72h obligation to notify the supervisory authority if risk warrants; that is the Controller's own obligation, not the Processor's.","notify_controller_sla":"without undue delay"},"changelog":[{"date":"2026-06-18","summary":"Initial registry. Slack canon. Anthropic + OpenAI + GHL disclosed. Erasure 'without undue delay'. GHL 4-year retention.","version":1},{"date":"2026-06-18","summary":"Centralised privacy lockup. Added meta.canonical_url + canonical_version + canonical_route_source. Added data_lanes section (Lane A = LL service customer, Lane B = DPA member processing, Lane C = John's lead-gen marketing). All surfaces (extension, MRR Leak Finder quiz, Skool Traffic Finder, growth/heat reports, README) now point at single canonical /privacy. Lane C surfaces add point-of-collection notice. Stripe added as Lane A sub-processor. Canonical policy bumped to v2.0 (3-lane).","version":2},{"date":"2026-06-18","summary":"Phases 3-6 \u2014 registry-as-source closes the loop. tools/privacy_sync.py renders DPA-lite + GHL DPA template + about-our-data + worker /privacy HTML from registry. Worker exposes /privacy/spec.json + /privacy/changelog.json + /privacy/spec.schema.json (ETag = registry fingerprint, CORS, 304 support). tools/privacy_query.py CLI. .claude/hooks/privacy-drift.sh pre-commit hook fails on drift OR registry-mod-without-bump. ghl_contact retention text restored ('on the next periodic purge').","version":3},{"date":"2026-06-18","summary":"privacy_mod.py pipeline shipped \u2014 single-command modification (DESCRIPTION + PRECISE modes) with rollback, hook integration, launcher tile","version":4},{"date":"2026-06-18","summary":"v1.5 DPA + SaaS-pattern e-sign: standard notify-on-update + continued use = acceptance, replaces re-sign-on-drift. Worker /dpa/sign idempotent; new routes /dpa/current + /dpa/changelog; new tools/notify_dpa_update.py CLI + email template.","version":5},{"date":"2026-06-19","summary":"Phase 0.5 Block 2 extension registry diffs. Added DC-07 install_telemetry; OP-07 ext_telemetry_engagement, OP-08 ext_uninstall_feedback, OP-09 ext_owner_pain_inbox, OP-10 ext_referral_chain; HD-08 owner_authored_text_allowed_lane_a; retentions for ext_install/user/admin_group/message/pain/sprint_signal (term of DPA), ext_telemetry (90d), ext_uninstall (2y), ext_referral_code (indefinite for attribution). DPA bumped v1.5 \u2192 v1.6. No new sub-processors.","version":6},{"date":"2026-06-19","summary":"Phase 1 ship: OP-11 dpa-update notification (owner Controller) with click-to-ack audit row; audited retention rules per compliance bundle 2026-06-19 (telemetry 90d \u2192 2y rolling, pain inbox DPA-term \u2192 1y rolling, new ext_dpa_ack 2y rolling). DPA bumped v1.6 \u2192 v1.7. No new sub-processors.","version":7},{"date":"2026-06-19","summary":"bumped DPA \u2192 v1.8","version":8},{"date":"2026-06-19","summary":"v1.9 DPA bundle (22 fixes): expanded-DPA claim removed; single-page claim removed; DPA acronym defined; admin-session updated (own OR Processor-as-admin); heat reports \u2192 reports; churn/signups \u2192 engagement+revenue metrics; Tier 1 requires DPA; avatars/bios \u2192 Tier 2 may-collect; Tier 1 anon-token clause; Sprint cohort clause; 14d \u2192 30d sub-processor notice; audit 14d \u2192 30d; Supabase AU primary; Google AI/Workspace added; GHL CRM desc updated; Cloudflare AU/EU/US; EU-only-region line removed; locally-on-John's-machine line removed; canonical URL updated; save-where \u2192 except-where; plain English rewritten. F22 location note in meta.notes.","version":9},{"date":"2026-06-19","summary":"Changed meta.canonical_url to custom domain privacy.ascendalliance.com.au (CF custom domain on leverage-lab-score worker, cert provisioned 2026-06-19)","version":10},{"date":"2026-06-19","summary":"DPA v2.0 \u2014 F1 asymmetric notice (LL\u2192Customer 7d sub-processor; Customer\u2192LL 30d audit); F2 governing law QLD AU + narrow consumer-protection carve-out only; F3 tied-to-identifier tier classification baked into DPA as explicit clause; F4 avatar-pool statistics \u2192 Tier 1 aggregate (shipped in error, reverted in v12). New registry fields: dpa.sub_processor_notice_days, dpa.audit_notice_days, dpa.tier_classification_note, identity.governing_law_consumer_note.","version":11},{"date":"2026-06-19","summary":"DPA v2.1 \u2014 revert F4: avatars always Tier 2. No Tier 1 avatar-pool exception. Avatars inherently identifying in practical use. DC-02 notes updated; tier_classification_note updated; DPA text updated.","version":12},{"date":"2026-06-19","summary":"bumped DPA \u2192 v2.2","version":13},{"date":"2026-06-19","summary":"v2.2 field updates: HD-06 desc + OP-06 notes \u2192 identifier-tied verbatim blocked, de-identified permitted (sentiment etc.); breach.notify_controller_sla_hours \u2192 notify_controller_sla='without undue delay' (no hard SLA; schema updated); dpa.anonymised_sharing_permitted=true + note; dpa.sub_processor_notice_note (asymmetric 7d/30d clarified); templates updated; schema updated.","version":14},{"date":"2026-06-19","summary":"bumped DPA \u2192 v2.3","version":14},{"date":"2026-06-19","summary":"bumped DPA \u2192 v2.4","version":15},{"date":"2026-06-19","summary":"Phase 2 Chunk 3-5: scraper public API endpoints + anon T0 telemetry (OP-12, OP-13, DC-06 extension)","version":16},{"date":"2026-06-19","summary":"appended to operations: {'id': 'OP-13', 'name': 'anonymous_install_telemetry', 'description': 'Phase 2 T0 anonymous telemetry: extension fires panel events (drawer opens, lock-clicks, action completions on generic BBs, stage scores derived in-browser) via POST /telemetry. install_token_hash = SHA-256 trunc-24 of an opaque random 32-hex token issued by GET /install/token. Token is a rate-limit lever \u2014 never identity-bound. Payload structurally rejects (worker + DB CHECK) any group/user identifier: group_slug, slug, admin_groups, user_id, skool_user_id, handle, display_name, email, group_id, gid, member_id, author_handle.', 'gate': 'none', 'requires_dpa': False, 'data_categories': ['DC-07'], 'notes': 'Lane Public / operational. No DC tie to a natural person at write time. Becomes correlated personal only if joined server-side with DC-05 (not done in T0). Retention 2y rolling per retention.ext_t0_telemetry. See apps/skool-scraper/sql/04_ext_t0_telemetry.sql.'}","version":17},{"date":"2026-06-19","summary":"bumped DPA \u2192 v2.5","version":18},{"date":"2026-06-22","summary":"Item B (Compliance verdict 2026-06-22 PM): added dpa.revocation_anonymisation_clarification \u2014 Recital 26 wording (once salt destroyed, data is no longer personal). Operational clarification only \u2014 NO DPA version bump, NO registry version bump (same data scope, clearer language). Replaces earlier Art 6(1)(f) framing flagged as category error.","version":18},{"date":"2026-06-22","summary":"Lane A retention_ref clarified for Watchtower auto-roll: active billing cycles + 6mo grace post-cancellation + 7y invoice/tax records (AU Income Tax Assessment Act). Operational clarification \u2014 no new data flow, no DPA bump.","version":19},{"date":"2026-06-22","summary":"Domain cutover: canonical surface URLs moved from privacy.ascendalliance.com.au \u2192 ascendalliance.com.au/{dpa,privacy,uninstall}/* (apex root, path-scoped). meta.canonical_url updated to https://ascendalliance.com.au/privacy. Marketing site at apex root continues to serve via CF Pages (only those path prefixes hit the worker). Old privacy.* subdomain remains live and 301-redirects to apex preserving full path + querystring, so all existing signed DPA links (/dpa/view/<token>?sig=...) keep working. New DPA mints use the apex domain. No data flow change, no DPA version bump (URL surface only).","version":20},{"date":"2026-06-22","summary":"Revocation split into SOFT (default) vs ERASE (explicit GDPR Art 17): /dpa/revoke is now SOFT \u2014 marks consent_log.revoke_type='soft', pauses Lane B collection, PRESERVES anon mapping; same-email re-sign reattaches historical data via UPDATE-not-INSERT on /dpa/sign. NEW /dpa/erase endpoint = irreversible Art 17 hard-delete (cascade purge + de-id + revoke_type='erase'). /api/dpa/status surfaces revoke_type + can_reattach. Migration patch34 adds consent_log.revoke_type CHECK ('soft','erase'); legacy revoked rows backfilled to 'erase' (their cascade-purge already ran). Behaviour change to data lifecycle; DPA wording change deferred (Compliance lane).","version":21},{"date":"2026-06-22","summary":"v22 Compliance bundle (REVIEW-compliance-overview-2026-06-22.md, 12 items). Shipped: Item 2 (Art 13 transparency disclosure block on /dpa/sign \u2014 Path B chosen, telemetry gated on sign event); Item 3 /me/erase endpoint (Owner Hat-2 self-erasure, Path B hard-delete, accepts email+slug OR ext-attested owner_id+slug+hmac; cascades consent_log revoke + Lane B purge + de-id + best-effort GHL contact delete); Item 11b URL canonicalisation (canonical link tag + default PUBLIC_BASE_URL fallback in score.js migrated from privacy.ascendalliance.com.au \u2192 ascendalliance.com.au; old subdomain 301 redirect preserved for existing signed DPA links); Item 11c structural T2 gate (_qmmRequireT2MemberNotice helper + stub /qmm/t2 endpoint that 403s with reason='member-notice-needed' if consent_log.member_notice_url is NULL); Item 12 schema migration patch35 (consent_log.owner_id nullable TEXT + identity_proof_type ENUM('email','ext_attested','manual_mint') DEFAULT 'email' + member_notice_url nullable TEXT + CHECK constraint at least one of owner_id/signer_email present; legacy rows backfilled identity_proof_type='email'). No DPA version bump (additive \u2014 same data scope + clearer compliance posture).","version":22},{"date":"2026-06-24","summary":"v24 \u2014 Request C (sanitised group description summaries) shipped. OP-14 notes extended: group description sanitised + PII-stripped (7 classes: email, phone, URL [skool.com self-refs preserved], social handle, payment keywords, street address, billing data) before any public surface; full original text never persisted to public surface; internally cached pre-scrub only for re-process. New engine tools/sanitise_description.py (scrub_pii + sentence-boundary truncate \u2264200 chars + ellipsis only on truncation). Quality gate tools/test_privacy_scrubber.py (13 PII strip cases + 3 false-positive preserve cases + skool.com whitelist \u2014 pre-commit hard-blocks on failure). tools/test_sanitise_description.py (12 boundary cases). Visible 'Condensed by Leverage Lab' stamp + hover tooltip on every render (apps/scoreboard-v2/group_template.html + build_group_pages.py \u2014 never presented as owner-direct copy). Granular sub-objection: new column suppress_description BOOLEAN on skool_group_audit (patch41) \u2014 NULL in export when set, separate from slug-level suppressed (sister kindness, owners can keep listing while hiding description). CLI tools/suppress_group.py to flip per slug. sync_directory_to_supabase.py writes description_summary + honours suppress_description in export. View v_skool_group_directory rebuilt to include description_summary with NULL-on-suppress. No DPA version bump (same data scope, same lawful basis Art 6(1)(f) \u2014 additive sanitiser layer).","version":24},{"date":"2026-06-24","summary":"v24 \u2014 DC-06 (public_group_metadata) examples extended with owner_social_urls: owner-authored public social links (instagramUrl, twitterUrl, linkedinUrl, websiteUrl, youtubeUrl) surfaced on the public Skool /about page (no login). T0 public group-level metadata, owner-published, not a member identifier. Wired into scoreboard-v2 pipeline (build_data union already preserves them; T0_FIELDS allowlist in build_group_pages.py extended), Supabase skool_group_audit (patch41 adds instagram_url/twitter_url/linkedin_url/website_url columns + v_skool_group_directory view), sync_directory_to_supabase upsert, and scoreboard-v2 UI (per-group page socials row + detail panel links, straight URLs, rel=noopener noreferrer nofollow, no affiliate params). No new lawful basis (still legitimate_interest, DC-06). No DPA version bump (additive public-metadata field, same data scope).","version":24},{"date":"2026-06-23","summary":"v23 \u2014 public directory lane (Lane D) + OP-14 publish_public_directory. directory.ascendalliance.com.au deploy bundle. Rulings 1-8 (REVIEW-compliance-public-deploy-directory-2026-06-23.md) + Rulings 9-13 (REVIEW-compliance-supabase-persistence-2026-06-23.md) shipped: Lane D (data_categories DC-05/DC-06/DC-01, lawful basis legitimate interest, no DPA, objection_endpoint, blocklist + suppression flag); OP-14 (publish_public_directory, cross_group allowed, gate none, Recital 30 B2B aggregate + Recital 47 commercial legit interest); Supabase skool_group_audit table (append-only, RLS service-role-only, CHECK constraint banning member identifiers, suppressed boolean for soft-delete on objection, indefinite retention via retention.group_aggregate). Mitigations M1-M5 (Compliance Supabase REVIEW) all landed in-cycle: registry entry (M1), CHECK constraint (M2), RLS lockdown (M3), suppressed flag (M4), sync .declare.yaml (M5). Pre-deploy B1/B2/B3 (privacy template extended for Lane D, per-page footer + Remove-listing link + canonical fix). Post-deploy P1-P5 (blocklist stub, robots.txt training-crawler block, Art 30 register row, est. prefix + methodology link, no comparative leaderboards). Sub-processor cf_pages_static added to lane_d storage list.","version":23},{"date":"2026-06-24","summary":"v25 \u2014 identity.contact_email moved from john@ascendalliance.com.au \u2192 privacy@ascendalliance.com.au (alias forwarding to john@). Anti-spam protection for scraped public surfaces (Lane D + scoreboard-v2 footer + per-group pages). Alias routed via Cloudflare email routing. DPAs + signed legal docs (delivered post-signing, not scraped) continue to use real John contact via per-document mailto. No new lawful basis, no DPA version bump (operational contact-channel change, same controller/processor identity Tinker Electric Pty Ltd). All ~2989 scoreboard-v2 group pages + index footer regenerated. Worker PRIVACY_HTML + UNINSTALL page swap handed off to ext-repo sister Builder session via outreach/HANDOFF-worker-email-alias-2026-06-24.md.","version":25},{"date":"2026-06-24","summary":"v26 \u2014 P1 self-service report scaffold (Builder under REVIEW cc0a82b \ud83d\udfe1 CONDITIONAL GREEN authorisation, \u00a7117-136). Adds Lane C surface row apps/scoreboard-v2/report.html (T0 self-service report form). Adds four new operations: OP-15 report_generation_t0 (Lane C, DC-05+DC-06, consent + legitimate-interest, GHL+Supabase EU sub-procs, audit_log retention); OP-16 report_generation_t1 (Lane B, DC-01+DC-05, contract via DPA, GHL+Supabase EU sub-procs, audit_log retention); OP-17 audit_log_retention (internal Art 28(3)(h) accountability log, 24mo rolling cap, 90d email-redaction window per C-c2); OP-18 email_transactional_ghl (Path C bridge per Q-d-2 \u2014 GHL transactional email, no new sub-processor; John chose Path C over SES Path B / CF Email Path A on 2026-06-24). Adds retention.audit_log (24mo rolling, automated purge via patch44 Supabase scheduled functions). Welcome-member variant honours C-b1 opt-in requirement: consent signal = signup-question Skool group field; consent_source = ghl_signup_question_skool_group recorded per generation. No DPA version bump (consistent with REVIEW \u00a7126 \u2014 same lawful basis, same sub-processors as currently disclosed for Lane B/C). No sub-processor change (Path C chosen, GHL already disclosed). Migration patch44_audit_log.sql applied to Supabase (audit_log table, RLS enabled, two scheduled functions). t0_purity_check.py + declare-schema `tier:` enum key + per-tool tier tagging shipped same bundle (structural enforcement of T0/T1 separation per C-a1/C-a2). Shared infrastructure: tools/report_shared/{template,email_ghl,pdf_storage,audit}.py. P2 (T0 lane) + P3 (T1 lane) + welcome variant follow in separate dispatches. Plan: outreach/PLAN-self-service-reports-P1-2026-06-24.md. As-Built: outreach/AS-BUILT-self-service-reports-P1-2026-06-24.md.\n","version":26},{"date":"2026-06-26","summary":"v28 \u2014 DPA Annex A 'Data items' table (JSON sidecar outreach/dpa-data-items.json + worker mirror): drop `field` key (programming endpoint names like 'group.name') AND `source_endpoint` key (URL paths like 'GET /{slug}/about (community.price)'). Owner-facing DPA artefact describes data in plain English only \u2014 endpoint surface + internal field-name leak both removed. Generator change in tools/privacy_sync.py _build_dpa_data_rows() \u2014 both keys removed from row dicts; docstring updated. Markdown table in outreach/dpa-lite.md \u00a73f unchanged \u2014 Field/source_endpoint were never rendered there. Internal apps/skool-tier-debugger/field_catalog.py still holds endpoint names (used for tier/DC lookups, etc.) \u2014 only the customer-facing DPA artefacts stop exposing them. No data flow change, no DPA version bump (presentation-layer cleanup, same data scope, same lawful basis). Compliance lane (Builder dispatched 2026-06-26 STOPPED + handed off to Compliance per outreach/DISCUSSION-builder-plan-deviation-dpa-annex-a-field-col.md + COMPLIANCE-TRIGGERS.md halt on privacy-registry.yaml edit).","version":28},{"date":"2026-06-26","summary":"v29 \u2014 DC-08 collector_identity added (plan-approved-collector-identity, outreach/PLAN-collector-identity-2026-06-26.md). Threads collector_user_id (Skool user id ONLY of the ext-running owner/admin) end-to-end: ext content.js (reuses existing users/me id, no new fetch) \u2192 harvester payload top-level body key (NOT inside fields \u2014 never PII-stripped) \u2192 worker /qmm/t1/admin-ingest passthrough \u2192 Supabase qmm_t1_admin_snapshots.collector_user_id (migration 20260626_patch46). Audit-trail provenance only: 'snapshot collected by user X under owner Y signed DPA'. Id-only, minimal-scope subset of DC-05 (no email/name/handle). legal_basis contract, requires_dpa true (collector = owner/admin on own granted house, within signed DPA scope; worker 403s pre-DPA). Client-asserted + server-side id-shape validated (provenance-grade, NOT authz-grade \u2014 no server-side HMAC/per-install auth added; existing no-HMAC finding documented separately, out of scope). data-source-registry.yaml NOT touched (runtime operator identity, not a Skool endpoint/field). No new sub-processor, no new worker route, no DPA version bump (additive id-only column under existing contract basis).","version":29},{"date":"2026-06-27","summary":"v30 \u2014 collector handle via PSEUDONYMISATION MAP (plan-approved-collector-handle, outreach/PLAN-collector-handle-map-2026-06-27.md). John explicitly chose the map design over the earlier per-row handle build, reasoning the GDPR trade-off himself (traceability vs data-minimisation). The earlier per-row collector_handle column build is CANCELLED \u2014 qmm_t1_admin_snapshots stays id-only (collector_user_id, NO handle column). The readable Skool handle is now stored ONCE per collector in a new tiny lookup table ext_collectors (user_id PK, handle, first_seen, updated_at \u2014 migration 20260627_patch47). Worker /qmm/t1/admin-ingest: snapshot insert UNCHANGED (id-only); ADDITIONALLY best-effort UPSERTs ext_collectors(user_id, handle, updated_at=now()) when a valid collector_handle (lowercase ^[a-z0-9-]{1,64}$) + collector_user_id are present \u2014 a map-upsert failure NEVER fails the snapshot ingest. Ext threads the handle transiently: content.js (me.handle, already resolved, no new fetch) \u2192 background.js setCollectorHandle \u2192 harvester payload top-level collector_handle key (sibling to collector_user_id, NOT inside fields). Viewer (tools/qmm_incoming_viewer.py) JOINs collector_user_id \u2192 ext_collectors.handle for display, with id fallback. GDPR basis: Art 4(5) pseudonymisation + Art 5(1)(c) minimisation; erasure of a collector = drop one ext_collectors row \u2192 every historical snapshot reverts to opaque id-only. legal_basis/requires_dpa UNCHANGED (still contract, still within signed DPA scope \u2014 collector = owner/admin on own granted house). data-source-registry.yaml NOT touched (runtime operator attribute, not a Skool endpoint/field). No new sub-processor, no new worker route, no DPA version bump (additive pseudonymisation map under existing contract basis, strictly data-min-improving over per-row storage).","version":30},{"date":"2026-06-28","summary":"v31 \u2014 Metabase registered as a SUB-PROCESSOR (plan-approved-client-embed-secure, outreach/PLAN-client-embed-secure-2026-06-28.md + DISCUSSION-compliance-client-embed-2026-06-28.md C1.5). Self-hosted Metabase (dashboard.ascendalliance.com.au, CF-fronted) now processes the Customer's OWN-GROUP AGGREGATE metrics to render a client dashboard surfaced via a short-TTL (\u2264300s), group-slug-LOCKED, HS256 signed embed JWT. Owner-safe aggregate views ONLY (vw_group_latest / vw_group_aaemr / vw_group_mrr_timeseries / vw_group_members_timeseries / vw_group_levels / discovery_rank) \u2014 NO member-level PII, NO cross-group, NO vw_sprint_signals (excluded at metabase_ro role grant). Access gate = signed DPA (worker /client/embed-link reads canonical consent_log via findExistingSignature; revoked \u2192 403). Lane B (post-consent owner-own-group analytics). New worker route reads consent_log (read-only, no schema change). Public-by-CF-bypass /embed/* path serves only signed-JWT-bound single-owner aggregate. Sub-processor 7-day LL\u2192Customer notice applies. No DPA version bump (additive analytics surface under existing contract basis, no new member-level processing \u2014 owner already sees their own aggregate in Skool admin).","version":31},{"date":"2026-06-29","summary":"v32 \u2014 DPA transparency completeness fix (REVIEW-dpa-data-alignment-2026-06-29.md). Added previously-undisclosed aggregate counts + collector identity to the customer-facing DPA data-items table via field_catalog.py additions: (1) engage.daily_active / engage.weekly_active / engage.monthly_active (DAU/WAU/MAU \u2014 T1/DC-01); (2) members.paying / members.free / members.cancelling / members.churned_status (MEMBER_STATUS cluster \u2014 T1/DC-01); (3) discovery.rank / discovery.category_rank (DISCOVERY cluster \u2014 T1/DC-01); (4) growth.visitors_30d (GROWTH cluster \u2014 T1/DC-01); (5) collector.user_id + collector.handle (DC-08 collector identity \u2014 T1/DC-08, already in registry but absent from customer-facing DPA table). No new data collection \u2014 disclosure completeness only. Same scope, same DC-01/DC-08 basis. banned count CONFIRMED not collected (harvester MEMBER_STATUS filters = active/cancelling/churned/free/paid only). DC-08 surfaced in DC_TO_DPA_SECTION + DC_TO_RETENTION_KEY + DC_STORED maps in privacy_sync.py. No DPA version bump.","version":32},{"date":"2026-06-29","summary":"v33 \u2014 entity-not-person fix for the public privacy policy. Removed every narrative 'John'/'John's' reference where John-the-person stood in for the company across the privacy pipeline; replaced with the legal entity (Tinker Electric Pty Ltd) / trading brand (Leverage Lab) per the legal-vs-product rule. Touched: privacy-html.j2 Lane C heading + Lane C body + Section 8 prospect notice (was 'a prospect or lead of John's', 'John's quiz', 'John acting as Controller', 'used by John to follow up'); about-our-data.html.j2 ('1:1 work with John' \u2192 trading_as, 'groups John owns' \u2192 legal_entity); registry data_lanes lane_c.who ('John's prospects' \u2192 entity) + meta.notes Lane C description. Added trading_as to render ctx in privacy_sync.py (ident.trading_as, default 'Leverage Lab') so templates resolve {trading_as}. NOT changed: contact_email (privacy@ascendalliance.com.au alias forwards to john@; functional routing) and identity.director 'John Missikos' (legally-required named director/authorised signatory \u2014 the human behind the entity, not a stand-in for it). data_lanes lane_c.label 'john_marketing_leadgen' left (code identifier, never rendered). Historical changelog summaries left verbatim (audit record). No data flow change, no new lawful basis, no DPA version bump (presentation/wording-only entity correction).","version":33},{"date":"2026-07-03","summary":"OP-19 ext_bug_report added \u2014 owner-initiated diagnostic self-report from extension (own ext_version/install_id/slug/role/DPA-state + last<=5 scrubbed error-buffer entries + own collect-status counts + user_agent + free-text note). PII-scrubbed server-side. Fires pre-DPA (exempt) so stuck/unsigned owners can report. Route POST /api/bug-report -> ext_bug_reports. gate none, requires_dpa false, DC-05+DC-07. Sibling of OP-08 + OP-09. No member data, no cross-group data, no tokens. (Retention key ext_bug_report added in companion changelog entry.)","version":34},{"date":"2026-07-03","summary":"Retention key ext_bug_report added = 1 year rolling (OP-19, DC-05+DC-07). Pegged to ext_owner_pain_inbox owner-free-text 1y window, NOT the 2y ext_telemetry window. Automated purge. Companion to the OP-19 ext_bug_report operation changelog entry.","version":35},{"date":"2026-07-03","summary":"Google Analytics disclosed as a WEBSITE-ANALYTICS item in the general privacy policy \u2014 NOT an Art-28 member-data DPA sub-processor. GA measures anonymous page-views on our PUBLIC Chrome Web Store listing page ONLY. Distinct from the existing Google (AI / Workspace Gemini) DPA sub-processor entry. Transparency disclosure only (more transparent = safer): GA sees only anonymous store-listing visitors, never owner or member data from the extension. Explicitly states GA is NOT used inside the extension \u2014 in-extension usage is measured by our own first-party telemetry (Supabase), never Google. CORRECTION of the earlier draft that placed GA in the DPA sub-processor list + bumped DPA current_version 2.5\u21922.6 + triggered a 7-day Art-28 sub-processor notice: GA is our own website/store-listing marketing analytics (controller-role, Lane C), does not process the owner's member data, so it belongs in the privacy policy, NOT the member-data DPA. Reverted DPA current_version 2.6\u21922.5, removed the spurious dpa.versions[] 2.6 entry, moved GA from sub_processors[] into a new website_analytics[] section. Rendered into privacy-policy surfaces only (worker /privacy HTML website-analytics block, about-our-data.html website-analytics block) via privacy_sync.py website_analytics render maps. NO change to DPA current_version, NO Art-28 sub-processor Customer notice, no new data collection, no member-level processing, no change to consent_log/DPA-signing flow.","version":36},{"date":"2026-07-03","summary":"patch66 (signal ext v0.20.24): OP-19 ext_bug_report gains owner_handle (DC-05 owner Skool handle) + owner_id (DC-08 owner's own Skool user id) so John knows WHO filed a bug report \u2014 currently the row carries group_slug + role + install_id but not the person. MINOR privacy change: adds the OWNER'S OWN self-identity (the same self.id + handle the DPA sign flow already resolves via __NEXT_DATA__ props.pageProps.self) to an EXISTING pre-DPA-exempt owner-own diagnostic op \u2014 NOT a new data category (DC-05/DC-08 already exist), NOT member data, NOT any other person, no new sub-processor. Best-effort: null when the ext can't resolve self (not on a Skool page). Retention unchanged (1y rolling ext_bug_report). Column added on ext_bug_reports via migration patch66 (owner_handle TEXT, owner_id TEXT, both nullable, idempotent). Worker validates + stores; ext reads the local qmm:userId key content.js persists + shows it in the bug-snapshot transparency preview so the owner sees their own handle is attached. No change to consent_log/DPA-signing flow, no member-level processing.","version":37},{"date":"2026-07-03","summary":"DPA-change-notification guardrails (Phases A-C): changelog schema fields dpa_version/dpa_change_class/affected_owners/change_axes documented; tools/dpa_change_notification_check.py gate wired (compliance_self_check + pre-commit + CI); privacy_mod.py requires --dpa-change-class on DPA bumps. Process/wiring change only, no DPA-version bump \u2014 dogfooded as MINOR. Doctrine: DOCTRINE-DPA-CHANGE-NOTIFICATION-2026-07-03.md.","version":38},{"date":"2026-07-07","summary":"New owner-safe Metabase-visible view vw_group_connectivity (patch53) for John's internal ops dashboard. One ops/freshness row per group_slug: DPA state (signed_at/revoked_at/version from consent_log), ext liveness (last_seen/version from ext_install_ping), admin_detected boolean (from ext_admin_group), data freshness timestamps (skool_dashboard_audit + qmm_t1_admin_snapshots). Counts/booleans/timestamps ONLY \u2014 NO member PII: signer identity, install_id, skool_user_id, owner_id, collector ids all excluded structurally at the view layer (patch51 doctrine). GRANT SELECT to metabase_ro (same tier as v31 Metabase sub-processor grant). No new personal-data field registered, no DPA-version bump (additive John-only ops analytics under existing basis; Metabase already a sub-processor since v31). dpa_change_class: MINOR.","version":39},{"date":"2026-07-17","summary":"DC-09 member_external_youtube_metrics added \u2014 per-member public YouTube view/velocity/leak-opportunity COUNTS derived from a member's own self-published linkYoutube handle, for the LOCAL 'My Members' qualify grid (member-report-hub) over groups John OWNS. HALT cleared per COMPLIANCE-TRIGGERS 'New data category (DC-XX)' (Compliance edits registry, never Builder) + DISCUSSION-compliance-new-data-category.md + ALIGNMENT-Q-compliance-member-qualify-grid.md. Existing DCs do NOT cover it: DC-02 member_handle_and_engagement_counts is Skool-INTERNAL engagement only; DC-06 owner_social_urls is the group-level public URL, explicitly 'not a member identifier' \u2014 neither covers a derived per-member EXTERNAL-platform metric. Traffic-light \ud83d\udfe1 own-house self-diagnostic (own-group axis GREEN, counts-not-content GREEN; \ud83d\udfe1 because named-member external-metric profiling; \ud83d\udfe1 not \ud83d\udd34 \u2014 own-group members only, member's own channel only, no stranger/cross-group profiling). Hard mitigations (conditions of approval): LOCAL only (127.0.0.1), cache member_qualify_cache.json gitignored, own-group gate via owned_groups.json (non-own slug refused), NEVER via publish_gate / never published / never sold, COUNTS ONLY (no video titles/thumbnails/descriptions/verbatim), in-memory compose ONLY (NOT a member_* DB join \u2192 stays out of the member_*-join HALT), cache-first manual --refresh (no cron/auto-hammer). personal:true, legal_basis legitimate_interest, requires_dpa:false. NO DPA version bump \u2014 never enters Lane B (not persisted to Supabase, not a sub-processor payload, reuses already-declared public-profile fetch, no new sub-processor). dpa_change_class: MINOR (no DPA-version change; additive local self-diagnostic data class, no member-level Lane B processing). Approved by John 2026-07-17.","version":40},{"affected_owners":"notify","change_axes":["prose"],"date":"2026-07-19","dpa_change_class":"minor","dpa_version":"2.6","summary":"v41 \u2014 classroom one-time product catalog (ext collection, GAP \u00a73.2 ADD 2). DC-01 owner_group_aggregate examples extended with classroom_one_time_count + classroom_one_time_total_cents + course_one_time_prices \u2014 the owner's OWN classroom per-course one-off prices (group's own public product pricing). NOT a new data category (fits existing DC-01: same owner-group-level money/product data, personal:false, legal_basis contract, Lane A). NOT member data, NOT verbatim member content, NOT cross-group, NOT stranger profiling. Collected by the extension in the owner's own browser (own-group gated: owner|admin role verified before collect \u2192 OP-02 cookied own-group lane), same-origin www.skool.com/{slug}/classroom SSR parse (allCourses[] \u2192 aggregate counts/prices only, no course titles transmitted, no member rows). Worker projects classroom_one_time_count + classroom_one_time_total_cents as typed scalars (mirrors \u00a73.1 cashflow split, same DC-01/T1 bucket). field_catalog.py adds the DPA data-items disclosure entries (T1/DC-01) surfaced in the customer-facing DPA data table. NO new sub-processor, NO new purpose/basis, NO retention change, NO new host permission, NO manifest widen, NO new DC-XX. DPA MINOR bump 2.5\u21922.6 (in-ext notify+ack, non-blocking; NO forced re-sign) \u2014 permitted in the Phase-D interim window. Ref REVIEW-compliance-classroom-onetime-2026-07-19.md (PASS \ud83d\udfe2, MINOR, member notice NO).","version":41},{"date":"2026-07-19","summary":"v42 \u2014 OP-20 serve_pool_percentile added: anonymised cross-tenant benchmark percentile serve (State-of-Skool pool, P1). k>=5 read-gate = structural answer to the cross-tenant-aggregate HALT trigger; cells <5 contributors return honest-null. Pool rows identity-detached at write time (no slug/name/install_id; CHECK bans identifier cols) = Recital 26 non-personal. Provenance split t1_consented vs t0_public_estimate NEVER blended in one cell (public stamped est.). DC-01 (already-collected owner-group aggregate) + DC-06 (public); NO new DC, NO new sub-processor, NO member-level data, NO new host permission. T1 pool inclusion already authorised by dpa.anonymised_sharing_permitted (benchmarks at Processor discretion) + revocation_anonymisation_clarification. NO DPA version bump \u2014 dpa.current_version stays 2.6; the 2.6 anonymised_sharing clause already covers this surface, so disclosure class = NONE (no in-ext notice owed). Lane Public sibling of OP-12. Ref REVIEW-compliance-pool-p1-clearance-2026-07-19.md (PASS, NONE).","version":42},{"date":"2026-07-23","summary":"v44 \u2014 DC-11 member_billing_status added (Compliance registration of PLAN-member-billing-capture-2026-07-23 \u00a7John's-decisions; HALT cleared per COMPLIANCE-TRIGGERS 'New data category (DC-XX)' + 'Change to privacy-registry.yaml' \u2014 Compliance edits registry, never Builder). DC-11 = per-member SUBSCRIPTION STATUS on a group the owner OWNS/admins under DPA: tier price, monthly/annual/free period, renewal countdown, trial state, grandfather flag + tier-price grandfathered at, join-era, price-joined-on, member level. Status/enum/int only. ALLOWED as a NEW distinct category (John 2026-07-23) \u2014 NOT a DC-04 exception: DC-04 payment INSTRUMENT (card, card_last4, stripe_id, payment_token, billing_address) stays permanently hard-denied by HD-02; DC-11 is subscription STATE only. Split enforced by the member_billing_snapshots CHECK constraint banning DC-04 columns. Inside (T1), personal:true, legal_basis contract, requires_dpa:true. AGGREGATE-ONLY outputs (tier-mix counts, grandfather pool, trial funnel, list-vs-collected gap, pricing-era/cohort archaeology) \u2014 per-member rows stored for counting, NEVER rendered/published/sold; name/bio/avatar excluded from analytics rows (DC-02 Tier-2 mirror); eras INFERRED est, never asked. CLASSIFIED MATERIAL per DOCTRINE-DPA-CHANGE-NOTIFICATION-2026-07-03 \u00a72.1 (new member-level requires_dpa data_category). BUT the MATERIAL DPA-version bump (2.6\u21922.7, forced re-sign) is DEFERRED + BLOCKED until DPA-change-notification Phase D ships (doctrine \u00a77 + John's Phase-D-first sequencing). This entry DEFINES DC-11 only \u2014 it stays DORMANT (NO Lane B storage, NO collection, NO operation wired) until Phase D lands and the material bump fires; the structured dpa_version/dpa_change_class/affected_owners fields attach to that later Phase-D bump entry, NOT here (dpa.current_version UNCHANGED at 2.6 this commit, so no notice fires + no re-sign). Only the own-group endpoint-recon lane (own-group data already under the current DPA, writes no new field) may proceed pre-bump. data-source-registry.yaml entry (endpoint + billing fields + dc_code:DC-11) DEFERRED to build-time \u2014 the billing endpoint is unknown until recon \u00a72a runs (DOCTRINE: data-source-registry = discovered Skool endpoints/fields). Verdict: outreach/REVIEW-compliance-member-billing-2026-07-23.md (PASS-with-conditions). Approved by John 2026-07-23.","version":44},{"date":"2026-07-22","summary":"v43 \u2014 DC-10 gateway_user_identity + OP-21 before_after_receipt_tool added (Compliance registration of Builder handoff DISCUSSION-compliance-about-tracker-uid-2026-07-22.md; HALT cleared per COMPLIANCE-TRIGGERS 'New data category (DC-XX)' \u2014 Compliance edits registry, never Builder). DC-10 = the signed-in GATEWAY user's own account identity (Google `sub` = uid, the cross-tool tenant key, + email), carried in the stateless mrr_session / SSO-handoff token \u2014 the TOOL OPERATOR, NOT a Skool member, NOT a Skool identifier. Sibling of DC-05 owner_identity but a Google-account id; used solely to isolate the operator's OWN private data (each tool keys rows AND uid = ?, 404-not-403). personal:true, legal_basis contract (the sign-in), requires_dpa:false. \ud83d\udfe2 own-house. OP-21 = before/after ad-readiness receipt tool (apps/skool-traffic-finder, about.mrrmax.com): PUBLIC /about only (DC-06, no cookie, no member identifiers) keyed to operator DC-10; re-homed onto the gateway shared Google session (SSO handoff) replacing the CF-Access email-OTP lock. gate gateway_session, cross_group allowed, requires_dpa:false, DC-06+DC-10. Zero-inside enforced end-to-end (box _assert_no_inside + tier always public; MRR/retention locked behind a future owner-DPA phase, never in this op). Screenshots in R2 (private, uid-namespaced, ownership-checked serve). NO new sub-processor (Worker + R2 already disclosed), NO member-level data, NO cross-group member data, NO new host permission. NO DPA version bump \u2014 dpa.current_version stays 2.6 (own-house operator identity + public /about source only; no Lane B member-level processing; disclosure class NONE). Migration 0004_experiments_uid.sql. Ref PLAN-about-tracker-gateway-auth-2026-07-22.md. Unblocks the about-tracker go-live registry tile flip.","version":43},{"date":"2026-07-23","summary":"OP-20 serve_pool_percentile notes: enumerate new surface owner-initiated PUBLIC share card (own metric vs anonymised k>=5 cohort) shipped leverage-lab-signal v0.20.150 (817bb7d). Light annotation of already-cleared surface; no gate/DC/DPA change. C5 of REVIEW-compliance-sharecard-percentile-2026-07-23.md; disclosure NONE, no DPA bump.","version":45},{"date":"2026-07-26","summary":"v46 \u2014 PATH RELOCATION + two accuracy corrections. NO substantive privacy change: no data category, operation, gate, lawful basis, hard-deny, sub-processor, retention period or data-subject right is added, removed or altered. NO DPA version bump (dpa.current_version stays 2.6), disclosure class NONE, no member notice owed, no re-sign. (A) RELOCATION \u2014 the standalone skool-scraper Cloudflare Worker repo (apps/skool-scraper, github.com/missikos/skool-scraper) was DECOMMISSIONED 2026-07-25: never deployed (wrangler reported no such Worker on the account, KV namespace id still a TBD placeholder, crons never fired). Its sql/ directory was nonetheless the ONLY schema source of truth for five files covering tables that ARE applied in production Supabase (group_scrape_snapshots, group_scores, public_post_metadata, pool_axis_bands/pool_contrib/pool_cohort_snapshot) plus one that is NOT applied (ext_t0_telemetry). All five .sql files were relocated byte-for-byte into skool-auditor under the existing supabase_schema_*.sql convention, each carrying a provenance header (origin repo + original filename + original commit + commit date + applied/not-applied status). Registry notes repointed at the new in-repo paths: DC-06 (public_post_metadata author-identifier ban), OP-12 (anonymous-lane scrape/score schema), OP-13 (ext_t0_telemetry CHECK), OP-20 (pool identity-detach CHECKs + k-anon shapes), retention.ext_t0_telemetry. Historical changelog entries left untouched as record. (B) ACCURACY CORRECTIONS \u2014 added by Compliance on review, NOT present in the Builder handover patch. The registry is the compliance source of truth and must not imply processing that is not happening: OP-13 anonymous_install_telemetry annotated DORMANT / NEVER LIVE (table not applied, serving worker never deployed, zero rows ever collected); retention.ext_t0_telemetry annotated as not currently running against any data; OP-12 serve_anonymous_group_score annotated NOT LIVE at the public-endpoint layer (POST /group/<slug>/score existed only in the never-deployed worker) while recording that its underlying tables ARE applied and read internally by heat_server.py. Both corrections are strictly NARROWING \u2014 they reduce claimed processing \u2014 and were prompted by the v2.5 changelog wording that these operations had shipped. Relocating a schema file is a RECORDS move, NOT a deployment decision; nothing was applied to Supabase by this work. GitHub remote github.com/missikos/skool-scraper deliberately LEFT INTACT; only the local working copy was trashed. Source: DISCUSSION-compliance-registry-mod-skool-scraper-relocation-2026-07-25.md; verdict: REVIEW-compliance-registry-scraper-relocation-2026-07-26.md.","version":46},{"date":"2026-07-26","summary":"v47 \u2014 ONE combined pass landing the rows queued by TWO prior Compliance artefacts, plus the FIRST registration of an api2 endpoint that had been fetched unregistered for 33 days. Landed in one bump deliberately: REVIEW-compliance-owner-graph-2026-07-26.md 10 and SPEC-compliance-public-community-graph-2026-07-26.md 8 both queued a v47, and the spec explicitly flagged the collision. NO DPA version bump \u2014 dpa.current_version stays 2.6, disclosure class NONE, no member notice owed, no re-sign, no new sub-processor, no MEMBER_TABLES migration, no consent_log touch. (A) PRIORITY FINDING \u2014 data-source-registry.yaml v1.9.0 -> v1.10.0 registers group_meta_api2_anon (GET api2.skool.com/groups/{slug}, anonymous cookieless, new transport api2_skool_anon, tier 0, DC-06) which tools/enrich_from_seed.py has fetched since commit d690ef2 on 2026-06-23 \u2014 33 days and 7,524 persisted rows \u2014 without ever appearing in the registry, and which tools/panel_observe.py now also uses. The prior spec called this a confirm-only step; it was not. Registered with the persisted-field list, an explicit deny_fields set headed by metadata.owner, and the AST-plus-behavioural control that enforces it. (B) DC-12 owner_public_portfolio added, NARROWED from the queued shape to the opaque metadata.created_by group-creator key ONLY (personal true, legitimate_interest, requires_dpa false, local_only, never_published, amber, refresh-in-place, gitignored); the queued owner handle / display name / owned-group set / profile badges and all /@handle portfolio assembly are recorded DORMANT and NOT COLLECTED, still gated behind the unwritten LIA (P-11) and the non-existent person-keyed blocklist (P-8). (C) OP-22 owner_portfolio_graph_internal added, NARROWED to collection-and-holding of the opaque key; the owner-portfolio GRAPH was not built (John took the group-centric route) so registering it as live would have implied processing that is not happening \u2014 the same error the v46 corrections narrowed on OP-12/OP-13. data_categories reduced to [DC-12]; the DC-06/DC-01 metadata in the same fetch stays under OP-01. (D) OP-23 publish_group_cohort_map added (cross_group allowed, requires_dpa false, gate k_anon_5, DC-06 + DC-01) covering the cohort percentile + band-movement blocks now rendered into the existing Lane D per-group pages; lawful basis INHERITED from OP-14, LIA amendment not fresh LIA, retention reuses group_aggregate. (E) HD-09 owner_graph_no_membership_or_private_nodes added, SCOPED to owner-portfolio processing so it cannot retro-break the pre-existing owner_profile location extraction; bound to the symbols that really enforce it (normalise_t0, OBS_FIELDS, FORBIDDEN_OBS_FIELDS, eligible). (F) HD-10 no_person_nodes_or_same_owner_edges_on_public_surfaces added with a CORRECTED symbol binding: the queued spec named cohort_stats.assert_single_segment, but that symbol enforces the OP-20 no-T0/T1-blend condition, not a person-node control, so it is bound to OP-23 instead and HD-10 carries OBS_FIELDS + FORBIDDEN_OBS_FIELDS + eligible. (G) retention.owner_graph added (refresh-in-place, no longitudinal person series, purge on objection) \u2014 governs a local gitignored store in Lane D legitimate-interest processing, not any Lane A/B contracted retention window, hence registry-MINOR with disclosure NONE. (H) Lane D notes + surfaces amended to describe derived cohort / band-movement publication, the k>=5 floor, the group-keyed-only constraint and the blocklist cascade to derived artefacts. FLAGGED, NOT FIXED (see the verdict): the fixed observation panel ships DAILY x 2,000 slugs on John's explicit instruction, which is verbatim the shape D-cond-1 of REVIEW-compliance-t0-expansion-bundle-2026-06-23.md caps at N <= 300 daily and its D1 WHAT-FAILS-THIS clause names \u2014 GDPR analysis is unchanged at 7x load (no new field, no new subject, non-personal group aggregates at finer time grain) but the aligned-with-Skool D1 posture verdict does change and D-cond-1 needs an explicit signed amendment. Also flagged: declaration drift on tools/enrich_from_seed.declare.yaml (declares DC-02 and DC-05 for an anonymous public group fetch), tools/panel_observe.declare.yaml (DC-12 output not declared, owner-keys.json retention keyed group_aggregate instead of owner_graph, purpose OP-22 missing), apps/scoreboard-v2/cohort_stats.declare.yaml (purpose OP-23 missing), and two stale parser references in data-source-registry (skoolcore.fetch.fetch_about / fetch_profile do not exist). Source: REVIEW-compliance-registry-v47-2026-07-26.md.","version":47},{"date":"2026-07-26","summary":"v48 \u2014 OP-24 report_cta_click_attribution added (Compliance registration of the Watchtower CTA per-recipient click attribution, REVIEW-compliance-watchtower-click-attribution-2026-07-26.md). Growth-report Watchtower CTAs now carry an opaque deterministic per-recipient code (r) + non-identifying placement code (p) on John's OWN go.mrrmax.com tracker hop; both are logged to the tracker's own D1 clicks row and STRIPPED before the outbound 302, so skool.com/<group>/plans receives NO identity params (only generic campaign UTMs). Net privacy IMPROVEMENT: the previous build forwarded utm_content=<group slug> through to skool.com, leaking recipient group identity to a third-party site on every click; that param is removed. Lane C, Art 6(1)(f) legitimate interest, direct sibling of OP-10 ext_referral_chain. NO new data category (DC-05 pseudonymous code), NO new retention key (reuses retention.ext_referral_code), NO new sub-processor (Cloudflare Worker + D1 already disclosed), NO member-level data, NO cross-group data, NO consent_log touch, NO cookie (no PECR trigger). DPA current_version stays 2.6; disclosure class NONE; no member notice owed. Change class MINOR.","version":48},{"date":"2026-07-27","summary":"v49 - OP-25 ext_onboarding_funnel added: per-install onboarding milestone counts (gate shown, connect tapped, hub arrived, Google authed, bearer received, first submit, report rendered, update welcome) on the EXISTING /api/ext-telemetry route into the EXISTING ext_telemetry table. Counts only, keyed on anonymous install_id. No new data category (DC-07), no new retention key (reuses retention.ext_telemetry 2y rolling), no new sub-processor, no new endpoint, no new host permission, no consent_log touch, no member data. Lane C owner-journey analytics, Art 6(1)(f). DPA version unchanged, disclosure class NONE. Change class MINOR.","version":49},{"date":"2026-07-27","summary":"v50 - DC-13 owner_own_channel_funnel_metadata + OP-26 funnel_audit_owner_channel added, landing condition C1 of outreach/REVIEW-compliance-yt-audit-pipeline-2026-07-27.md so the YT -> Skool funnel-audit pipeline (plan steps 3-7) unblocks. DC-13 = public YouTube metadata about the DELIVERABLE RECIPIENT'S OWN channel (video id/URL/title, short-vs-long, upload date, view count, derived velocity + views-per-day, their OWN description + pinned-comment text, extracted outbound links), collected for an owner-requested audit of that owner's own public channel and delivered privately back to that same owner. Subject == recipient == channel owner; subject-role = Controller/customer, NOT profiled member. personal true, legal_basis CONTRACT Art 6(1)(b), requires_dpa true, traffic_light amber, never_published true in the C-02/C-13 aggregate-and-third-party sense (the subject receiving their own report is not a publish). OP-26 = the operation, gate own_group_or_dpa_signed, requires_dpa true, data_categories [DC-13, DC-05], carrying the fail-closed recipient gate (C2: dpa_status_query signed+not-revoked via the canonical worker/consent_log reader, OR a structured recorded request with its source; plus the config slugs must match the slugs the scan actually found in the channel's own descriptions; refuse otherwise, no soft-fail, no --force, no attestation checkbox) and the expiry rule (C4: every mint sets non-null expires_at, default 90 days, overridable DOWNWARD only). ID NOTE: the review proposed OP-25; OP-25 was taken by ext_onboarding_funnel at v49 in the meantime (collision flagged 2026-07-27 in tools/yt_funnel_analyse.declare.yaml), so the next free id OP-26 was allocated. EXPLICITLY REJECTED ALTERNATIVES, per the review: no DC-09 carve-out (DC-09 is deliberately absolute; conditionalising it forces every future reader to re-derive which mitigations apply) and no OP-16 stretch (OP-16's text is a cookied-Skool-admin report; stretching it to a PUBLISHED deliverable from a DIFFERENT platform would make the registry text mean nothing). DC-09 boundary is stated in BOTH directions and is AST-enforced at build time (C6): DC-09 numbers never enter a DC-13 artefact, and DC-13 collection never back-fills the qualify cache / leak census. DPA-CHANGE CLASSIFICATION (DOCTRINE-DPA-CHANGE-NOTIFICATION-2026-07-03): MINOR. dpa.current_version UNCHANGED at 2.6, disclosure class NONE, affected_owners = notify (no forced re-sign), no member notice owed. Rationale: the data subject is the OWNER (Lane A, ll_service_customer, Controller = Tinker Electric Pty Ltd), not their members, so this is not the Lane B member-level expansion that forced DC-11 dormant; no member-level data is processed, nothing enters Lane B, and the DPA text describing what we process on a Controller's behalf is unchanged. requires_dpa true on DC-13/OP-26 is a RECIPIENT-IDENTITY GATE (the signed DPA is the machine-checkable proof of the customer relationship), not a claim of member-level processing. NO new sub-processor (Google US + Cloudflare already disclosed), NO new retention key (mint/access rows fall under retention.audit_log), NO new endpoint (group_about_public + group_meta_api2_anon already registered in data-source-registry.yaml, so C-14 is satisfied; any NEW Skool path registers BEFORE the fetch ships), NO consent_log write (the gate READS it). Precedent deliverable id=92 (token VqbxZJBnjkx9QeSavevQMQ) was ruled COMPLIANT and is NOT revoked. STILL OPEN, NOT RESOLVED BY THIS BUMP: C5 - YouTube Data API vs yt-dlp vs hybrid is John's call on the record before the scanner ships, and player_client=web_embedded as a stated 429 workaround comes out either way. Authority: outreach/REVIEW-compliance-yt-audit-pipeline-2026-07-27.md C1.","version":50}],"data_categories":[{"description":"Group-level metrics (MRR, member count, churn, signups, structure, traffic mix)","examples":["mrr","member_count","paid_count","free_member_count","churn_rate","signups_7d","signups_30d","classroom_one_time_count","classroom_one_time_total_cents","course_one_time_prices"],"id":"DC-01","legal_basis":"contract","name":"owner_group_aggregate","personal":false},{"description":"Member identifiers (handle, display name, level, join date) + engagement counts within recency window + derived heat score + public profile data (avatar URL, bio)","examples":["handle","name","level","joined_at","post_count","comment_count","like_count","heat_score","avatar_url","bio"],"id":"DC-02","legal_basis":"contract","name":"member_handle_and_engagement_counts","notes":"Avatars are Tier 2: avatars are inherently identifying in practical use (even without an explicit handle tie, they are recognisable); no Tier 1 exception. Public bios are inherently identifying and stay Tier 2 regardless. Not collected at Tier 0 or Tier 1. Not an Art 9 special category.","personal":true,"requires_dpa":true},{"description":"Verbatim text bodies (posts, comments, DMs) tied to a specific member","id":"DC-03","name":"member_post_content_verbatim","never_collected":true,"notes":"Hard-denied by gate (HD-01) regardless of DPA. Truncated to 200 chars if leaked.","personal":true},{"description":"Member email, phone, IP, payment data, billing address","id":"DC-04","name":"member_contact_identifiers","never_collected":true,"notes":"Hard-denied by gate (HD-02). Permanent block.","personal":true},{"description":"Owner name, email, Skool handle, contact preferences (GHL CRM record)","examples":["name","email","skool_handle","phone"],"id":"DC-05","legal_basis":"contract","name":"owner_identity","personal":true},{"description":"Group /about + /plans + public post metadata (post_id, posted_at, like_count, comment_count, pinned, is_question) \u2014 all public on Skool, no cookie needed. NEVER includes author_handle, author_name, or verbatim post body.","examples":["group_name","niche","member_count","about_text","tier_stack","post_id","posted_at","like_count","comment_count","pinned","is_question","owner_social_urls"],"id":"DC-06","legal_basis":"legitimate_interest","name":"public_group_metadata","notes":"Phase 2 Chunk 5: post-metadata fields added \u2014 author identifiers structurally banned (see public_post_metadata DO block + ext_t0_telemetry CHECK constraint in supabase_schema_public_post_metadata.sql + supabase_schema_ext_t0_telemetry.sql). owner_social_urls = owner-authored public links (instagramUrl, twitterUrl, linkedinUrl, websiteUrl, youtubeUrl) shown on the public Skool /about page, no login needed \u2014 group-level public metadata, not a member identifier.","personal":false},{"description":"Extension install_id, browser_ua, panel engagement events (panel_open/close, panel_visible_pct, bb_done, bb_skip), uninstall pings. Counts only \u2014 no member content.","examples":["install_id","browser_ua","panel_open","panel_close","panel_visible_pct","bb_done","bb_skip"],"id":"DC-07","legal_basis":"contract","name":"install_telemetry","notes":"Standalone non-personal. Becomes correlated personal data when joined server-side with DC-05. Lane A.","personal":false},{"description":"Skool user id of the owner/admin running the extension who collected a T1 admin snapshot. Id ONLY \u2014 no email, name, or handle. Reuses the already-fetched users/me id (no extra fetch). Audit-trail provenance: links each qmm_t1_admin_snapshots row to the collecting account under the owner's signed DPA.","examples":["collector_user_id"],"id":"DC-08","legal_basis":"contract","name":"collector_identity","notes":"Minimal-scope owner/admin self-identity (a strict subset of DC-05 owner_identity: id only, no contact fields). Collector = the owner/admin acting on their own granted house, so same contract basis + within their signed DPA scope. Stored on qmm_t1_admin_snapshots.collector_user_id. Client-asserted + id-shape validated server-side \u2014 provenance-grade, not authz-grade. Never collected at T0. Not an Art 9 special category. PSEUDONYMISATION MAP (v30, plan-approved-collector-handle 2026-06-27): the readable Skool handle is stored ONCE per collector in the ext_collectors lookup table (user_id PK \u2192 handle), NEVER per snapshot row \u2014 snapshots remain id-only (collector_user_id). The viewer JOINs id \u2192 handle for display. This is Art 4(5) pseudonymisation + Art 5(1)(c) minimisation: facts reference the opaque user_id; the re-identifying handle lives in a single separate map. Erasure of a collector = drop the one ext_collectors row \u2192 every historical snapshot reverts to opaque id-only (no row-by-row scrub of the fact table). The handle is transmitted transiently in the ingest payload (collector_handle, top-level body key, lowercase ^[a-z0-9-]{1,64}$ validated) and best-effort upserted to the map; a map-upsert failure never fails the snapshot ingest.","personal":true,"requires_dpa":true},{"description":"Per-member public YouTube view/velocity/leak-opportunity COUNTS derived from the member's own self-published linkYoutube handle (the public URL they placed on their Skool /about). Numeric metrics only: views_30d, upload velocity, age-decayed leak-opportunity (adj_opp). Scoped to members of a group John OWNS/admins. LOCAL-only self-diagnostic for the My Members qualify grid.","examples":["yt_views_30d","yt_velocity","yt_adj_opp","yt_channel_active"],"id":"DC-09","legal_basis":"legitimate_interest","local_only":true,"name":"member_external_youtube_metrics","never_published":true,"notes":"\ud83d\udfe1 own-house self-diagnostic (CLAUDE.md privacy traffic-light). Own-house axis GREEN (member is in a group John owns per skoolcore/owned_groups.json); counts-vs-content axis GREEN (metrics only). \ud83d\udfe1 not \ud83d\udfe2 because it attributes an EXTERNAL-platform numeric metric to a named member (per-person profiling beyond a bare Skool engagement count) \u2014 DC-02 is Skool-INTERNAL engagement only, DC-06 owner_social_urls is the group-level URL and explicitly 'not a member identifier', so neither covers this derived per-member external metric. \ud83d\udfe1 not \ud83d\udd34 because: own-group members only, member's OWN self-published channel only, no cross-group/stranger profiling. HARD MITIGATIONS (conditions of approval, DISCUSSION-compliance-new-data-category.md \u00a74): LOCAL only (127.0.0.1, no public endpoint); cache member_qualify_cache.json gitignored (never enters repo/leaves machine); own-group gate via owned_groups.json (non-own slug refused); NEVER routed through skoolcore.publish_gate / never published / never sold; COUNTS ONLY \u2014 NO video titles/thumbnails/descriptions/transcripts/comments/verbatim; in-memory compose ONLY \u2014 NOT a Supabase member_* table join; cache-first manual-refresh (--refresh only, --max ceiling, no cron/auto-hammer). No DPA version bump \u2014 never enters Lane B processing (not persisted to Supabase, not a sub-processor payload); reuses the already-declared public-profile fetch (enrich.py::fetch_socials / challenge_qualify worker audit) so no new sub-processor. The member->owned-community->DPA composite (warm-pilot-owner detection) is assembled in memory from DC-02 identity + canonical consent_log (dpa_status_query.py) at request time; personal profile, same LOCAL/never-published mitigations, NOT a member_* DB join. Not an Art 9 special category. Approved 2026-07-17 (John) for own-group local self-diagnostic use.","personal":true,"requires_dpa":false,"tier":"local_self_diagnostic","traffic_light":"amber"},{"description":"Identity of a signed-in Leverage Lab GATEWAY user (the TOOL OPERATOR, not a Skool member): the Google account subject id (`sub`, the stable cross-tool tenant key) + email, carried in the stateless mrr_session / SSO-handoff token. This is the person USING a suite tool (e.g. the before/after receipt tool), authenticated once via the gateway's shared Google sign-in. NOT member data, NOT a Skool identifier \u2014 it is the user's own account under which they operate their own private workspace.","examples":["uid","google_sub","email"],"id":"DC-10","legal_basis":"contract","name":"gateway_user_identity","notes":"\ud83d\udfe2 own-house: the user's OWN account identity, used solely to isolate their OWN private data (each tool keys rows by this uid, AND uid = ?, 404-not-403 on a miss \u2014 GATEWAY-CONVERGENCE-spec.md \u00a72.5). Sibling of DC-05 owner_identity but a Google-account id rather than a Skool handle; the gateway is the identity service (canonical users table anchored on google_sub, \u00a72.1). No DPA (no member-level processing; contract basis = the sign-in). Not published, not sold, not cross-joined across users. First surface: apps/skool-traffic-finder before/after receipt tool (experiments.uid = operator's Google sub). Not an Art 9 special category.","personal":true,"requires_dpa":false},{"description":"Per-member SUBSCRIPTION STATUS on a group the owner OWNS/admins under signed DPA: which price tier the member sits on, whether they pay monthly/annual/free, renewal countdown, trial state, whether they are grandfathered (and at which tier price), when they joined (the price-era key), and the rate they came in on. Numeric/enum status only. This is subscription STATE \u2014 NOT the payment INSTRUMENT (card, billing address, Stripe id, payment token), which stays permanently hard-denied under HD-02 / DC-04.","examples":["tier_price_cents","billing_period","renewal_days","trial_state","trial_ends_days","grandfathered","grandfathered_at_tier_price_cents","joined_at","price_joined_on_cents","member_level"],"id":"DC-11","legal_basis":"contract","name":"member_billing_status","notes":"Inside (T1) only, DPA-gated (member-level = owner DPA or own-group; membership alone is not a lawful basis \u2014 [[no-member-tier-without-dpa]]). ALLOWED as a NEW distinct category, NOT a DC-04 exception. The DC-04 / DC-11 split is the core ruling (John 2026-07-23): DC-11 = subscription STATUS (which plan, when it renews, trial state, grandfather era); DC-04 = payment INSTRUMENT (card, card_last4, stripe_id, payment_token, billing_address) = still permanently hard-denied by HD-02. The split is enforced structurally by a CHECK constraint on the member_billing_snapshots table banning every DC-04 payment-instrument column.\nExisting DCs do NOT cover it: DC-01 is group-level aggregate (personal:false) not per-member; DC-02 is Skool-internal handle/engagement/level, not billing state; DC-04 is the hard-denied payment instrument. Hence a new DC.\nAGGREGATE-ONLY OUTPUTS: per-member rows are stored for COUNTING (tier-mix distribution, grandfathered pool, trial funnel, annual-vs-monthly split, revenue-at-list vs collected gap, and pricing-era / cohort archaeology) and are NEVER rendered per-member, NEVER published, NEVER sold (aggregate-only; C-02/C-13). The motivating bug: a single blended ARPU cannot decompose tier mix \u2014 mix must be COUNTED, never inferred from a mean.\nMINIMISATION: name / bio / avatar are EXCLUDED from stored analytics rows (mirror the DC-02 Tier-2 rule); if raw capture unavoidably includes them they live ONLY in the DPA-covered raw store, never in analytics tables or reports. Pricing eras are INFERRED from grandfather rates \u00d7 join dates, stamped est \u2014 never asked of the owner.\nSEQUENCING (DORMANT until Phase D): adding this member-level requires_dpa category is MATERIAL per DOCTRINE-DPA-CHANGE-NOTIFICATION-2026-07-03 \u00a72.1. Per John's decision + doctrine \u00a77, the MATERIAL DPA-version bump (2.6\u21922.7, forced re-sign) is BLOCKED until DPA-change-notification Phase D ships. This entry DEFINES DC-11 but it stays DORMANT \u2014 NO Lane B storage, NO collection, NO operation wired \u2014 until Phase D lands and the material bump fires. Only the own-group endpoint-recon lane (reads own-group data already covered by the current DPA, writes no new field) may proceed pre-bump.\nNot an Art 9 special category. Approved by John 2026-07-23 (PLAN-member-billing-capture-2026-07-23 \u00a7\"John's decisions\"). Verdict: outreach/REVIEW-compliance-member-billing-2026-07-23.md.\n","personal":true,"requires_dpa":true,"tier":"inside_t1"},{"description":"Opaque group-CREATOR key: metadata.created_by (32-char id; no name, no email, no handle) read from the anonymous api2 group blob that is already fetched once per slug. Held so a published denominator can be de-duplicated by operator (63,765 group rows is NOT 63,765 operators). NARROWED AT REGISTRATION to the opaque key ONLY \u2014 the wider owner-portfolio shape specified by REVIEW-compliance-owner-graph-2026-07-26.md (owner handle, display name, owned-group set, profile badges, via the /@handle endpoint) is DORMANT and NOT COLLECTED.","examples":["created_by_opaque_key"],"id":"DC-12","legal_basis":"legitimate_interest","local_only":true,"name":"owner_public_portfolio","never_published":true,"notes":"SCOPE AS REGISTERED (v47). The ONLY field collected is the opaque\nmetadata.created_by key. Parse boundary: tools/enrich_from_seed.py\nnormalise_t0 (allow-list construction). Also read by tools/panel_observe.py.\nStorage: snapshots/panel/owner-keys.json ONLY \u2014 REFRESH-IN-PLACE, gitignored,\nnever a dated series (no person-keyed history, spec P-12), never published,\nnever routed through skoolcore.publish_gate. Source endpoint registered as\ngroup_meta_api2_anon in data-source-registry.yaml (v1.10.0).\n\nWHY personal true. The key is a stable pseudonymous identifier of a natural\nperson (the group creator). Art 4(5) \u2014 pseudonymisation is not anonymisation.\nIt is NOT DC-06 (personal false, group metadata) and NOT DC-01 (group\naggregate); holding it under either would understate the class. Own row for\nthe same reason DC-09 got one.\n\nWHY amber not green \u2014 it attributes groups to a person, in principle\nidentifiable. Amber not red because opaque key only, no name/email/handle\ncollected, local, never published, no cross-group profile rendered anywhere.\n\nHAZARD + CONTROL (structural, not tribal knowledge). The SAME api2 payload\ncarries a sibling metadata.owner JSON-STRING blob holding first_name /\nlast_name / name / email (DC-05 + HD-02 class). It is NEVER read. Enforced by\ntools/test_created_by_owner_blob.py, which locks the boundary BOTH\nbehaviourally (a fully-populated owner blob reaches nothing in the output)\nAND structurally by AST (normalise_t0 source may not reference an owner key,\nmay not gain a second json.loads, may not contain any email access) \u2014 a\nbehavioural-only test would still pass if PII entered the process and was\nthen discarded. Mirrored in data-source-registry group_meta_api2_anon\ndeny_fields.\n\nDORMANT / NOT COLLECTED \u2014 recorded so the boundary is explicit, NOT so it may\nbe built: owner_handle, owner_display_name, owned_public_group_slugs,\nbadge_disc_top, badge_mrr_status, and any use of the /@handle profile\nendpoint for portfolio assembly. Those remain gated by\nREVIEW-compliance-owner-graph-2026-07-26.md P-1..P-14, in particular the\nwritten LIA (P-11) and the person-keyed suppression list (P-8), NEITHER of\nwhich exists. Collecting them without those is a fresh HALT.\n\nNo DPA impact: never enters Lane B, no processor relationship, no member-level\ndata, no new sub-processor, reuses an already-issued fetch (zero marginal\nrequest). Disclosure class NONE. Not an Art 9 special category.\n\nAuthority: REVIEW-compliance-owner-graph-2026-07-26.md 10, as NARROWED by\nREVIEW-compliance-registry-v47-2026-07-26.md.\n","personal":true,"requires_dpa":false,"tier":"local_self_diagnostic","traffic_light":"amber"},{"description":"Public YouTube metadata about the DELIVERABLE RECIPIENT'S OWN channel, collected to audit that person's own YouTube -> Skool funnel and handed straight back to them: video id / URL / title, video type (short vs long), upload date, public view count, derived upload velocity and views-per-day, the recipient's OWN video description text and OWN pinned-comment text, and the outbound links extracted from those. Subject-role = Controller / customer who asked for the audit, NOT a profiled member. Collected only for a recipient who passes the machine-checked recipient gate, and disclosed only to that same person.","examples":["yt_video_id","yt_video_url","yt_video_title","yt_video_type","yt_published_at","yt_view_count","yt_views_per_day","yt_upload_velocity","yt_own_description_text","yt_own_pinned_comment_text","yt_outbound_links"],"id":"DC-13","legal_basis":"contract","name":"owner_own_channel_funnel_metadata","never_published":true,"notes":"PURPOSE. An owner-requested YouTube funnel audit of the owner's OWN public\nchannel, delivered privately to that owner. Subject == recipient == channel\nowner: the three collapse to one person by construction, and the gate\n(OP-26) refuses the run when they do not. Lawful basis = CONTRACT,\nArt 6(1)(b) \u2014 the audit is the service the customer asked for \u2014 NOT the\nweaker \"public data about a public figure\" argument. Lane A\n(ll_service_customer, Controller = Tinker Electric Pty Ltd). This is NOT\nLane B member-level processing: the data subject is the CUSTOMER, not their\nmembers.\n\nWHY ITS OWN CATEGORY. DC-01 is group-level aggregate (personal false). DC-05\nis Lane A owner CRM identity. DC-06 owner_social_urls is the group-level URL\nand is explicitly not a member identifier. DC-09 is the opposite operation\n(see below). None covers per-video external-platform metadata plus the\nowner's own authored copy, held for a deliverable. Own row for the same\nreason DC-09 and DC-12 got one.\n\nDC-09 BOUNDARY \u2014 HOLDS IN BOTH DIRECTIONS. DC-09\n(member_external_youtube_metrics) authorises exactly one thing: the LOCAL,\ncounts-only, never-published My Members qualify grid, in which John profiles\nhis own members WITHOUT asking them. Every DC-09 mitigation is the price of\nthat unilateral profiling and does NOT travel here. DC-13 inverts every axis\n(purpose, subject knowledge, subject role, lawful basis, output). The two\nlanes must not converge:\n  (1) DC-09 values NEVER enter a DC-13 artefact \u2014 no qualify-grid number,\n      no leak-census row, no member_qualify_cache.json read;\n  (2) DC-13 collection NEVER back-fills the qualify cache, the leak census\n      or any other DC-09 artefact \u2014 no write, no re-use, no warm cache.\nEnforced structurally, not by comment: an AST-level isolation test ships\nwith the scanner (compliance condition C6), same technique as\ntools/test_created_by_owner_blob.py, because a behavioural test still passes\nif DC-09 values enter the process and are then discarded.\n\nNEVER_PUBLISHED \u2014 in the C-02 / C-13 sense: aggregate-and-third-party. The\nsubject receiving their OWN report is not a publish. What is barred is any\ndisclosure to anyone other than the named subject: no public report, no\nnamed case study, no Skool post, no carousel, no testimonial, no screenshot\nin content, and no artefact carrying more than one channel's data (that is a\ncross-owner join -> C-02 HALT). Delivery is private-only and fail-closed\nthrough skoolcore.publish_gate (is_public false, audience private). Any of\nthose changing is an independent HALT requiring a fresh Compliance verdict.\n\nVERBATIM. The only text captured is copy the recipient authored themselves\nand published on their own channel (their video titles, their descriptions,\ntheir pinned comment). HD-08 owner-authored logic, not HD-01 / DC-03 member\nverbatim. No third party's content is read, quoted or stored.\n\nRETENTION. The deliverable is expiry-bound: every mint sets a non-null\nexpires_at, default 90 days, config-overridable DOWNWARD only (compliance\ncondition C4). Mint / access rows fall under retention.audit_log (24mo\nrolling, 90d email redaction). No new retention key.\n\nSUB-PROCESSOR. Google (US) is ALREADY a disclosed sub-processor. No new\nsub-processor is added by this category.\n\nDPA IMPACT. Disclosure class MINOR, no dpa.current_version bump, no forced\nre-sign, no member notice owed: no member-level data is processed, nothing\nenters Lane B, the DPA text describing what we process on a Controller's\nbehalf is unchanged. requires_dpa true here is a RECIPIENT-IDENTITY GATE\n(the signed DPA is the machine-checkable proof of the customer relationship),\nnot a claim that member-level processing occurs. Not an Art 9 special\ncategory.\n\nAuthority: outreach/REVIEW-compliance-yt-audit-pipeline-2026-07-27.md\ncondition C1. Precedent deliverable id=92 (token VqbxZJBnjkx9QeSavevQMQ)\nruled COMPLIANT there and NOT to be revoked.\n","personal":true,"requires_dpa":true,"tier":"owner_deliverable","traffic_light":"amber"}],"data_lanes":[{"controller":"Tinker Electric Pty Ltd","data_categories":["DC-05"],"id":"lane_a","label":"ll_service_customer","lawful_basis":"contract","policy_section":"Section 2 (Lane A) + Section 3 row A + Section 9","processor":"Tinker Electric Pty Ltd","retention_ref":"ghl_contact","storage":["ghl_us","stripe_us"],"who":"LL service customers (owner signups for extension / Sprint / paid Lab)"},{"controller":"Customer (group owner)","data_categories":["DC-02"],"id":"lane_b","label":"customer_member_processing","lawful_basis":"contract_via_customer","policy_section":"Section 2 (Lane B) + Section 3 row B + Section 7","processor":"Tinker Electric Pty Ltd","retention_ref":"member_level_dpa","storage":["supabase_eu"],"who":"Members of Customer's groups, under signed DPA"},{"controller":"Tinker Electric Pty Ltd","data_categories":["DC-05"],"id":"lane_c","label":"john_marketing_leadgen","lawful_basis":"consent_plus_legitimate_interest","point_of_collection_notice":"required","policy_section":"Section 2 (Lane C) + Section 3 row C + Section 8","processor":"Tinker Electric Pty Ltd","retention_ref":"ghl_contact","storage":["ghl_us"],"surfaces":["apps/Money-models-quiz (MRR Leak Finder)","apps/skool-traffic-finder","GHL landing pages (audit required)","apps/scoreboard-v2/report.html (T0 self-service report form)"],"who":"Tinker Electric Pty Ltd (trading as Leverage Lab) prospects \u2014 quiz responders, contact form fills, mailing list signups"},{"controller":"Tinker Electric Pty Ltd","data_categories":["DC-05","DC-06","DC-01"],"id":"lane_d","label":"public_directory","lawful_basis":"legitimate_interest","notes":"Public-directory lane. Aggregate B2B group metadata + owner public identity\n(handle, display name) published on directory.ascendalliance.com.au with\nper-group SEO pages. Lawful basis = GDPR Art 6(1)(f) legitimate interest;\nRecital 30 B2B aggregate. Owner objection honoured via blocklist + suppression\nflag (soft-delete) on next nightly build. No member-level data; structurally\nenforced by skool_group_audit CHECK constraint. See OP-14.\nv47 AMENDMENT (derived group-level publication). The lane now also publishes\nDERIVED group-level figures on the same per-group pages under OP-23: cohort\npercentile (category x size_band x membership_model ladder), cohort cell\nlabel and size, and group-keyed band-movement over time from a fixed\nstratified observation panel. All k>=5 gated with honest-null below the\nfloor; T0-public and T1-consented segments never blended in one cell;\nmovement carries a mandatory coverage stamp. Group-keyed ONLY \u2014 no person\nnode, no same-operator edge, no owner key in any published artefact\n(HD-10), no top-N page, no compare-two surface, estimated MRR never used as\na visual dimension. Private, zero-member and objection-blocklisted slugs are\nexcluded from cells and from every derived count, so the existing\nslug-keyed objection route (directory-blocklist.json + objection_endpoint)\nremains sufficient for this lane and cascades to the derived artefacts.\nLawful basis unchanged (Art 6(1)(f)); this is an LIA AMENDMENT to OP-14,\nnot a fresh LIA, because the unit of analysis stays group-centric.\nSeparately, an opaque group-creator key is held INTERNALLY under DC-12 /\nOP-22 for denominator de-duplication; it is never published on this lane.\n","objection_endpoint":"https://directory.ascendalliance.com.au/object?slug={slug}","policy_section":"Section 2 (Lane D) + Section 3 row D + Section 11","processor":"Tinker Electric Pty Ltd","retention_ref":"group_aggregate","storage":["cf_pages_static","supabase_au"],"surfaces":["apps/scoreboard-v2 (directory.ascendalliance.com.au)","apps/scoreboard-v2 per-group cohort percentile + band-movement blocks (OP-23, k>=5)","supabase.skool_group_audit (server-side mirror)"],"who":"Public Skool community owners (group + owner publicly self-advertised on skool.com/<slug>)"}],"dpa":{"anonymised_sharing_note":"Identity-stripped, aggregated data with no re-identification path may be shared in benchmarks, reports, or examples at the Processor's discretion. Only figures tied to the Customer's identity are subject to the sharing restriction.","anonymised_sharing_permitted":true,"audit_notice_days":30,"current_date":"2026-07-19","current_version":"2.6","revocation_anonymisation_clarification":"After consent withdrawal, we cryptographically anonymise historical data series.\nThe salt enabling re-identification is destroyed at the moment of withdrawal.\nOnce anonymised, the resulting data is no longer personal data under GDPR\n(Recital 26) and may be retained for product improvement. We cannot\nre-identify you from this data. Re-signing creates a new data series with no\nlink to your previous history.\n","sub_processor_notice_days":7,"sub_processor_notice_note":"sub_processor_notice_days is LL\u2192Customer notice when adding/replacing a sub-processor (7 days, or less where urgency requires). audit_notice_days is Customer\u2192LL notice for audit requests (30 days). Asymmetric by design.","tier_classification_note":"Tier classification depends on whether data is structurally tied to a member identifier.\nTier 1: aggregated counts, level distribution, engagement clusters \u2014\nanonymous tokens (e.g. MEMBER_001) that cannot be back-resolved to a real handle.\nAvatars are always Tier 2: inherently identifying in practical use; no Tier 1 exception.\nTier 2: any member-level data tied to a real member identifier (handle, display name,\navatar, biography). Requires DPA signed + privacy notice in the Customer's group.\nThe moment data becomes identifier-tied, it moves to Tier 2 regardless of which feature surfaced it.\n","variants":[{"audience":"Skool owners \u2014 quick read, GHL Documents flow","file":"outreach/dpa-lite.md","id":"dpa_lite"},{"audience":"DEPRECATED \u2014 superseded by dpa_lite","file":"outreach/dpa-template.md","id":"dpa_full"},{"audience":"GHL Documents & Contracts template body","file":"outreach/ghl-dpa-template.md","id":"ghl_dpa"}],"versions":[{"date":"2026-06-10","summary":"Initial DPA-lite. Skool owner = controller, Leverage Lab = processor.","version":"1.0"},{"date":"2026-06-17","summary":"Added Tinker Electric entity, AU governing law.","version":"1.1"},{"date":"2026-06-18","summary":"Dropped unregistered trading-as claim.","version":"1.2"},{"date":"2026-06-18","summary":"2-stage value flow. Owner-side metrics allowed under DPA. Member content hard floor.","version":"1.3"},{"date":"2026-06-18","summary":"Slack canon. Sub-processors disclosed (Anthropic/OpenAI/GHL). Erasure 'without undue delay'. GHL contact retention 4y.","version":"1.4"},{"date":"2026-06-18","summary":"Switched from re-sign-on-bump to standard SaaS notify-on-update pattern; continued use = acceptance; clarified erasure rights as termination path (Section 11 rewrite).","version":"1.5"},{"date":"2026-06-19","summary":"Block 2 extension features added: identity capture (Lane A), telemetry events (Lane A), uninstall ping (operational), owner pain inbox (Lane A, owner-authored text only), Skool affiliate passthrough (existing Lane A), short ref_code referral chain (Lane A/legitimate interest). SaaS notify-on-update fires to all signed owners.","version":"1.6"},{"date":"2026-06-19","summary":"Phase 1 ship \u2014 OP-11 dpa_update_notification operation added (cross-tier, notifies group owner Controller on registry/DPA material bump; explicit click-to-ack writes ack row to consent_log with ack_timestamp + acknowledged_dpa_version + registry_fingerprint_at_ack). Audited retention rules per compliance bundle 2026-06-19: ext_telemetry 90d \u2192 2y rolling; ext_pain DPA-term \u2192 1y rolling; new ext_dpa_ack 2y rolling. No new sub-processors.","version":"1.7"},{"date":"2026-06-19","summary":"v1.8: Drop lite naming (title + body); honest never-collects floor (3b rewrite); add Tier 0/1/2 scope clarification section (3e).","version":"1.8"},{"date":"2026-06-19","summary":"v1.9 bundle (22 fixes): drop expanded-DPA claim + single-page claim; DPA acronym defined on first use; admin-session wording updated (own session OR Processor-as-admin); heat reports \u2192 reports generalised; churn/signups \u2192 engagement and revenue metrics; Tier 1 requires DPA clarified; avatars/bios moved to Tier 2 may-collect; Tier 1 anonymous-token clarification added; Sprint/cohort cohort-sharing clause added; sub-processor 14d \u2192 30d notice; audit notice 14d \u2192 30d; Supabase AU primary; Google AI / Workspace added; GoHighLevel CRM desc updated to include telemetry; Cloudflare regions = AU/EU/US; EU-only-region-on-request line removed; locally-on-John's-machine line removed; leverage-lab.com/about-our-data \u2192 canonical worker URL; save-where \u2192 except-where; plain English block rewritten; gdpr-register.md location noted as historical/cleanup deferred.","version":"1.9"},{"date":"2026-06-19","summary":"v2.0 \u2014 F1: asymmetric notice periods (LL\u2192Customer 7 days for sub-processor changes; Customer\u2192LL 30 days for audits); F2: governing law narrowed to QLD AU + consumer-protection-only carve-out; F3: tied-to-identifier tier classification rule baked in as explicit clause. (F4 avatar-pool Tier 1 carve-out shipped in error \u2014 reverted in v2.1.)","version":"2.0"},{"date":"2026-06-19","summary":"v2.1 \u2014 revert F4: avatars always Tier 2 (no Tier 1 anonymous-stat exception). Avatars are inherently identifying in practical use regardless of whether they are explicitly tied to a handle. Removes 'avatar-pool statistics' from Tier 1 list in DC-02, tier_classification_note, DPA text, and template.","version":"2.1"},{"date":"2026-06-19","summary":"v2.2 \u2014 F1: clarify asymmetric notice (7d sub-proc LL\u2192Customer, 30d audit Customer\u2192LL); F2: HD-06 + OP-06 updated \u2014 identifier-tied verbatim blocked, de-identified permitted (sentiment etc.); F3: breach SLA = 'without undue delay' not hard 72h (customer retains own 72h regulator obligation); F4: anonymised sharing explicitly permitted (identity-stripped aggregated data at Processor discretion).","version":"2.2"},{"date":"2026-06-19","summary":"v2.3 \u2014 no unbacked email promises found in DPA text (sign-confirmation is code TODO only, not a DPA promise). Add honest termination-HOW sentence to \u00a711: email now, self-serve link on roadmap. No other changes.","version":"2.3"},{"date":"2026-06-19","summary":"v2.4 self-serve revoke: GET/POST /dpa/revoke worker routes (revoke-intent HMAC), sign-confirmation email with permalink + revoke link, cascade Lane B data purge on revoke, consent_log.revoked_via column added, Section 11 rewritten.","version":"2.4"},{"date":"2026-06-19","summary":"v2.5 \u2014 Phase 2 (scraper public API): DC-06 extended with public_post_metadata fields (post_id, posted_at, like_count, comment_count, pinned, is_question \u2014 author identifiers structurally banned); retention.ext_t0_telemetry added (2y rolling); OP-12 serve_anonymous_group_score + OP-13 anonymous_install_telemetry shipped earlier in this version (Lane Public, legitimate interest, no identity binding stored).","version":"2.5"},{"date":"2026-07-19","summary":"v2.6 \u2014 MINOR (in-ext notify+ack, non-blocking). DC-01 owner_group_aggregate examples extended with the owner's OWN classroom one-time product catalog: classroom_one_time_count + classroom_one_time_total_cents + course_one_time_prices (per-course one-off prices, group's own public product pricing \u2014 NOT member data, NOT verbatim member content, NOT cross-group). Same DC-01 category, same lawful basis (contract, Lane A), same worker + Supabase AU sub-processors, no retention change, same-origin www.skool.com fetch (no new host permission). Additive money-diagnostic field only (Skool UI hides one-off sales; this restores the owner's own number). Ref REVIEW-compliance-classroom-onetime-2026-07-19.md (PASS, MINOR, member notice NO).","version":"2.6"}]},"hard_deny_rules":[{"code_symbols":["VERBATIM_MAX_CHARS","_strip_verbatim_tied_to_identifier","_VERBATIM_FIELDS"],"constraint_refs":["DC-03"],"description":"Verbatim text > N chars in a record that also carries a member identifier","id":"HD-01","name":"verbatim_member_content_tied_to_identifier","threshold_chars":200},{"code_symbols":["_HARD_DENY_FIELDS","_strip_hard_deny"],"constraint_refs":["DC-04"],"description":"Contact / payment / IP identifier fields are hard-denied regardless of context","fields":["email","phone","mobile","ip","ip_address","payment_method","card_last4","stripe_id","payment_token","address","billing_address","raw_email","hashed_email"],"id":"HD-02","name":"hard_deny_contact_identifiers"},{"code_symbols":["_check_cross_group"],"description":"Records from multiple distinct group slugs in one batch are denied by default","exempt_operations":["OP-01","OP-03"],"id":"HD-03","name":"cross_group_join_forbidden_on_cookied","notes":"OP-01 (anonymous /about) + OP-03 (leaderboard) are explicit exemptions \u2014 both are public, no member identifiers."},{"code_symbols":["require_dpa","dpa_status","GDPRPendingDPA"],"description":"Cookied scrape against a group requires either own-ownership or a signed DPA","id":"HD-04","name":"cookied_lane_must_be_own_or_dpa_signed"},{"code_symbols":["_load_dpa_registry","dpa_status"],"description":"An opt-out / erasure request blocks all subsequent processing for the identifier","id":"HD-05","name":"opt_out_blocklist_overrides_all_lanes"},{"code_symbols":["filter_external_llm_payload","_HANDLE_PATTERNS","_EXTERNAL_LLM_PATTERNS"],"constraint_refs":["DC-03","OP-06"],"description":"External LLM payload containing verbatim text tied to a member identifier (handle pattern + > 200 chars) is blocked. De-identified verbatim (handles stripped, names tokenised to MEMBER_001, no back-resolution path) may be sent for analysis such as sentiment classification.","id":"HD-06","name":"external_llm_no_member_identified_verbatim","threshold_chars":200},{"code_symbols":["filter_outbound"],"description":"Publishing a named report publicly requires explicit consent on owner's own group","id":"HD-07","name":"public_publish_requires_consent_and_own_group","notes":"Public-by-consent doctrine. See PUBLISH-DOCTRINE.md."},{"constraint_refs":["OP-09","DC-05"],"description":"Owner pain-inbox text is OWNER-authored under Lane A contract. Allowed. Does NOT cross HD-01 (verbatim member content) because authorship is the owner themselves.","id":"HD-08","name":"owner_authored_text_allowed_lane_a","notes":"Bind via RLS author check \u2014 only the authoring owner may submit/read their own pain entries."},{"code_symbols":["normalise_t0","FORBIDDEN_OBS_FIELDS","OBS_FIELDS","eligible"],"constraint_refs":["DC-12","OP-22"],"description":"Within owner-portfolio processing (DC-12 / OP-22): any groupsMemberOf-derived edge, any node or edge for a group with privacy == 1 or public != True, and any per-person badge chip on a shared surface are hard-denied. Denial is at the PARSE boundary, not at render.","fields":["groupsMemberOf","groups_member_of","dailyActivities","daily_activities","email","pictureProfile","picture_profile","pictureBubble","myersBriggs","myers_briggs","location","discTop","disc_top","mrrStatus","mrr_status","actStatus","act_status","googleTagId","hyrosScriptUrl","metaConversionsStatus","lpDescription"],"id":"HD-09","name":"owner_graph_no_membership_or_private_nodes","notes":"SCOPE \u2014 this rule binds OWNER-PORTFOLIO processing (DC-12 / OP-22) and any\nfuture owner-graph build. It is NOT a global retro-ban on every field named\nin `fields`. In particular `location` is an EXISTING declared extraction on\nthe owner_profile endpoint (DC-05, Lane D, enrich.py fetch_socials) that\npredates this rule and is untouched by it; if that extraction should stop,\nthat is a separate review, not a side effect of this row.\nREVIEW-compliance-owner-graph-2026-07-26.md P-2 denies these fields INSIDE\nthe graph scope, which is what is recorded here.\n\nLIVE ENFORCEMENT TODAY, by symbol:\n  tools/enrich_from_seed.py normalise_t0 \u2014 allow-list construction; the\n    output dict is built key-by-key, so an unlisted payload field cannot\n    pass through. Locked behaviourally + by AST in\n    tools/test_created_by_owner_blob.py.\n  tools/panel_observe.py OBS_FIELDS \u2014 the dated observation series is an\n    explicit 14-field allow-list.\n  tools/panel_observe.py FORBIDDEN_OBS_FIELDS \u2014 asserted on every projected\n    row; includes groupsMemberOf, groupsCreatedByUser, created_by, owner,\n    email, description and the profile-picture keys. An injected field does\n    not pass.\n  apps/scoreboard-v2/cohort_stats.py eligible \u2014 private (privacy == 1),\n    zero/unbandable-member and objection-blocklisted slugs are dropped from\n    cells AND from every derived count.\n\nNOT YET BOUND (no symbol exists because the artefact does not exist): the\nper-person badge-chip denial and the same-owner edge filter have no render\nsurface to bind to. Building either requires the symbol to be named here\nfirst, per the standing pattern.\n\nAuthority: REVIEW-compliance-owner-graph-2026-07-26.md P-1, P-2, P-3, P-6;\nREVIEW-compliance-registry-v47-2026-07-26.md.\n"},{"code_symbols":["OBS_FIELDS","FORBIDDEN_OBS_FIELDS","eligible"],"constraint_refs":["OP-23","OP-14","DC-12"],"description":"No published artefact may contain owner_key / owner_user_id / created_by; no edge whose semantics are shared-operator; no node or edge for a group with privacy == 1, public != True, or total_members == 0; no per-person badge chip; no published top-N ranking page or compare-two surface.","fields":["owner_key","owner_user_id","created_by","createdBy","same_operator","owner_handle","owner_name","badge_disc_top","badge_mrr_status"],"id":"HD-10","name":"no_person_nodes_or_same_owner_edges_on_public_surfaces","notes":"The publish-boundary rule for every group-level public derivation (OP-23)\nbuilt on the Lane D directory (OP-14).\n\nWHY the opaque key cannot be published even unnamed: pseudonymous is not\nanonymous (Recital 26, Art 4(5)), and because every connected group page\nALREADY names its owner under OP-14, publishing the link structure\npublishes the person. The opaque key does not hide the operator; it labels\nthem. Same-operator edges and publicly-rendered owner keys are therefore\nRED on any public surface and stay internal (OP-22).\n\nPermitted despite the above: a single aggregate POPULATION statistic\nderived from the keys (for example \"N groups are run by ~M operators\") \u2014\nno subject, no cell, no re-identification path. Publish the scalar, never\nthe key, never the mapping, never a per-group \"also runs N groups\" line.\n\nCODE SYMBOLS \u2014 corrected binding. The queued spec named\ncohort_stats.assert_single_segment for this rule; that symbol enforces the\nOP-20 no-T0/T1-blend condition, which is a k-anonymity/provenance control,\nNOT a person-node or owner-edge control. It is bound to OP-23 instead. The\nsymbols that actually enforce HD-10 today are panel_observe.OBS_FIELDS +\nFORBIDDEN_OBS_FIELDS (owner key can never enter the dated series that feeds\nthe public movement layer) and cohort_stats.eligible (private, zero-member\nand blocklisted slugs never reach a published cell). A dedicated\npublish-boundary strip assertion does not yet exist as a symbol and must be\nnamed here when the map/constellation surfaces (spec S1/S4) are built.\n\nBlocklist cascade: an objection on a slug drops the node, drops its edges,\nand drops it from any k<5-adjacent cell. It may remain inside k>=5\naggregate counts, which are non-personal and outside Art 17 reach.\n\nAuthority: SPEC-compliance-public-community-graph-2026-07-26.md 1c, 3.1,\n4.2, 8; Rulings 8.3 / 8.4; REVIEW-compliance-registry-v47-2026-07-26.md.\n"}],"identity":{"abn":"83 660 188 278","contact_email":"privacy@ascendalliance.com.au","controller_role":"Data controller for product analytics and CRM contacts","director":"John Missikos","governing_law":"Queensland, Australia","governing_law_consumer_note":"except where consumer-protection laws of the Customer's place of residence cannot be excluded by contract \u2014 in which case those specific consumer-protection provisions apply","jurisdiction":"Australia","legal_entity":"Tinker Electric Pty Ltd","processor_role":"Data processor for owner-shared group/member-level metrics under DPA","trading_as":"Leverage Lab"},"meta":{"canonical_route_source":"apps/leverage-lab-chrome-ext/worker/score.js (PRIVACY_HTML)","canonical_url":"https://ascendalliance.com.au/privacy","canonical_version":"2.0","last_updated":"2026-07-27","notes":"Privacy + DPA docs ALWAYS state the slackest defensible posture \u2014 never tighter\nthan the GDPR statutory floor. Slack canon = we meet it trivially + GDPR satisfied.\nThe canonical_url is the single source of truth for ALL surfaces (extension,\nMRR Leak Finder quiz, Skool Traffic Finder, growth reports, GHL landing pages).\nNo per-tool duplicate privacy policies \u2014 point at canonical.\nv2.0 (3-lane canonical) covers Lane A (LL service customer), Lane B (DPA member\nprocessing) and Lane C (Tinker Electric Pty Ltd / Leverage Lab lead-gen marketing) in distinct sections.\nCLEANUP NOTE (F22): outreach/gdpr-register.md lives under outreach/ for historical\nreasons. Do not move it \u2014 too many cross-references. Future cleanup: consolidate\nto a top-level compliance/ directory. Tracked here so it is not forgotten.\n","registry_version":50,"source_of_truth":true},"operations":[{"cross_group":"allowed","data_categories":["DC-06"],"description":"Public /about page scrape \u2014 no cookie, no member identifiers","id":"OP-01","name":"scrape_anonymous_about_page","requires_dpa":false},{"cross_group":"forbidden","data_categories":["DC-01","DC-02"],"description":"Cookied scrape using primary or secondary lane on owner's own group or granted group","id":"OP-02","name":"scrape_cookied_own_or_granted","requires_dpa":true},{"cross_group":"allowed","data_categories":["DC-01","DC-06"],"description":"Skool Games leaderboard public scrape \u2014 group-level aggregates across categories","id":"OP-03","name":"scrape_leaderboard_games_host","notes":"Leaderboard is public group aggregates with no member identifiers. The /games/<category> host context legitimately surfaces many groups in one fetch \u2014 cross_group is intentional and harmless.","requires_dpa":false},{"data_categories":["DC-02"],"description":"Member engagement heat report \u2014 counts only, never verbatim content","gate":"own_group_or_dpa_signed","id":"OP-04","name":"heat_report_member_engagement","requires_dpa":true,"verbatim_truncation":200},{"data_categories":["DC-01","DC-02"],"description":"Owner-facing growth report PDF \u2014 owner consent required","gate":"owner_consent_explicit","id":"OP-05","name":"publish_growth_report_owner_consent","requires_dpa":true},{"data_categories":["DC-01"],"description":"Payload sent to external LLM API (Anthropic / OpenAI / etc.) for diagnostic generation","gate":"aggregate_only","id":"OP-06","name":"external_llm_payload","notes":"Verbatim content tied to a member identifier is hard-denied by HD-06. De-identified verbatim (handles stripped, names tokenised to MEMBER_001) may be sent for analysis (e.g. sentiment classification) \u2014 HD-06 permits this as the identifier link is broken.","requires_dpa":false},{"data_categories":["DC-01","DC-07"],"description":"Extension panel engagement events (panel_open, panel_close, panel_visible_pct 0-1, bb_done, bb_skip). Counts only, no member content.","gate":"own_install","id":"OP-07","name":"ext_telemetry_engagement","notes":"Under owner Lane A contract. Retention 90d rolling (see retention.ext_telemetry).","requires_dpa":true},{"data_categories":["DC-07"],"description":"Anonymous uninstall ping with install_id only (no skool_user_id).","gate":"own_install","id":"OP-08","name":"ext_uninstall_feedback","notes":"Operational. Becomes correlated personal only if server-side joined to DC-05. Retention 2y rolling.","requires_dpa":false},{"data_categories":["DC-05"],"description":"Owner-authored pain messages via in-extension dialog. Owner identity + owner-authored text.","gate":"own_install","id":"OP-09","name":"ext_owner_pain_inbox","notes":"Lane A contract. Owner-authored text only; never member content (see HD-08). Retention: term of DPA.","requires_dpa":true},{"data_categories":["DC-05"],"description":"Short 6-char ref_code in install URLs for attribution chain.","gate":"own_install","id":"OP-10","name":"ext_referral_chain","notes":"Lane A / legitimate interest. ref_code never carries skool_user_id; resolved server-side only. Retention indefinite for attribution chain.","requires_dpa":false},{"data_categories":["DC-05","DC-07"],"description":"Notifies group owner Controller when registry or DPA version bumps materially. Owner explicit click-to-ack writes ack_timestamp + acknowledged_dpa_version + registry_fingerprint_at_ack to consent_log. Cross-tier \u2014 fires for any tier with a signed DPA.","gate":"own_install","id":"OP-11","name":"dpa_update_notification","notes":"Lane A. Notification target is the group OWNER (Controller of their members' data), NOT members; members are notified separately by the owner via their group privacy notice if Lane B is active. Click-to-ack pattern per bundle G6/C3 recommendation. Auto-purge OP-11 events 2y rolling (see retention.ext_dpa_ack).","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-06"],"description":"Phase 2 T0 public API: serve cached server-computed Money Model Score for a group_slug via POST /group/<slug>/score. Score computed from anonymous-lane scrape of public /about + /plans only (DC-06). No member identifiers in payload; no install\u2194slug binding stored. group_slug is the explicit T0 quasi-identifier surface (Recital 30 \u2014 B2B aggregate use, lawful basis legitimate interest, disclosed in install notice per bundle \u00a7 L1). Rate-limited per IP / per anonymous install token.","id":"OP-12","name":"serve_anonymous_group_score","notes":"Lane Public. Lawful basis legitimate interest. group_slug is quasi-identifier per Recital 30 but B2B aggregation use \u2014 not personal data of a natural person. No install identity stored anywhere. See supabase_schema_group_scrape_snapshots.sql + supabase_schema_group_scores.sql (relocated 2026-07-25 from the decommissioned apps/skool-scraper worker repo). STATUS 2026-07-26 (Compliance accuracy correction, registry v46): the public serve endpoint described above (POST /group/<slug>/score) is NOT LIVE. It existed only in the standalone skool-scraper Cloudflare Worker, which was never deployed (wrangler: no such Worker on the account; KV namespace id still a TBD placeholder; crons never fired) and was DECOMMISSIONED 2026-07-25. The underlying tables group_scrape_snapshots + group_scores ARE applied in production Supabase and are read INTERNALLY only (heat_server.py /api/scraper/health dashboard); no public API serves them. The v2.5 changelog wording that this operation shipped overstated deployment. Entry retained as the cleared design + structural constraint set; standing up a public serve re-enters Compliance.","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-05","DC-06","DC-01"],"description":"Publish public Skool community directory on directory.ascendalliance.com.au \u2014 per-group SEO pages naming owner + group + estimated MRR derived from public price \u00d7 member count. Source: public Skool /about + public leaderboard. No member-level data. Server-side mirror in supabase.skool_group_audit (append-only, RLS service-role-only, CHECK constraint banning member identifiers, suppressed flag honours owner objection). Lawful basis: GDPR Art 6(1)(f) legitimate interest + APP 3 + APP 6.","gate":"none","id":"OP-14","name":"publish_public_directory","notes":"Lane D. Recital 30 + 47 \u2014 public B2B aggregate + owner public identity used\nunder legitimate interest. NOT consent-based (Art 6(1)(f), not Art 6(1)(a)).\nOwner objection honoured via outreach/directory-blocklist.json + suppression\nflag (soft-delete). Hard-delete escalation on explicit demand.\nMRR-estimation structurally flagged \"est.\" in every render (see Ruling 8.1).\nNo comparative leaderboards (Ruling 8.3). No member-level data\n(Ruling 2 + structural CHECK constraint per Ruling 9.2).\nSkool ToS posture: tolerated grey (skool-finder.com precedent +\nskool-tos-DOA-verdict). robots.txt blocks training crawlers.\nGroup description sanitised + PII-scrubbed BEFORE publish (7 PII classes:\nemail, phone, URL [skool.com self-refs preserved], social handle, payment\nkeywords, street address, billing data); full original text never on public\nsurface; internally cached pre-scrub only for re-process. Display includes\nvisible \"Condensed by Leverage Lab\" stamp + hover tooltip pointing at\nskool.com/<slug>. Granular sub-objection: suppress_description column on\nskool_group_audit honoured at view layer (NULL in export when set), separate\nfrom slug-level suppressed flag. See Request C / C-cond-1..5\n(outreach/REVIEW-compliance-t0-expansion-bundle-2026-06-23.md).\n","requires_dpa":false},{"data_categories":["DC-07"],"description":"Phase 2 T0 anonymous telemetry: extension fires panel events (drawer opens, lock-clicks, action completions on generic BBs, stage scores derived in-browser) via POST /telemetry. install_token_hash = SHA-256 trunc-24 of an opaque random 32-hex token issued by GET /install/token. Token is a rate-limit lever \u2014 never identity-bound. Payload structurally rejects (worker + DB CHECK) any group/user identifier: group_slug, slug, admin_groups, user_id, skool_user_id, handle, display_name, email, group_id, gid, member_id, author_handle.","gate":"none","id":"OP-13","name":"anonymous_install_telemetry","notes":"Lane Public / operational. No DC tie to a natural person at write time. Becomes correlated personal only if joined server-side with DC-05 (not done in T0). Retention 2y rolling per retention.ext_t0_telemetry. STATUS 2026-07-26 (Compliance accuracy correction, registry v46): this operation is DORMANT and has NEVER been live. The table public.ext_t0_telemetry is NOT APPLIED in production Supabase, and the worker that would have served POST /telemetry + GET /install/token (the standalone skool-scraper Cloudflare Worker) was never deployed and was DECOMMISSIONED 2026-07-25 \u2014 therefore no telemetry has ever been collected under OP-13 and no rows exist. Relocating the schema file on 2026-07-25 was a RECORDS move, NOT a deployment decision; nothing was applied to Supabase. Entry retained as the cleared design + structural constraint set; activation (applying the table + wiring a live endpoint) re-enters Compliance. Schema (UNAPPLIED): supabase_schema_ext_t0_telemetry.sql.","requires_dpa":false},{"data_categories":["DC-05","DC-06"],"description":"Generate T0 (public-data-only) self-service report for any requester via public form. Inputs limited to anonymous /about + public group metadata (DC-06). Requester email captured for delivery only (DC-05). No DPA required (no member-level data touched).","gate":"none","id":"OP-15","name":"report_generation_t0","notes":"Lane C (lead-gen marketing surface) + Lane D (public group data input). Lawful basis = consent (Art 6(1)(a) \u2014 explicit click on public form) + legitimate interest (Art 6(1)(f) \u2014 Recital 30 B2B aggregate already covers Lane D source data). Sub-processors: GHL (US, already disclosed) for email delivery + CRM upsert; Supabase EU for audit_log row. Retention_ref: audit_log (24mo rolling, 90d email redaction). Welcome variant: same OP, consent_source = ghl_signup_question_skool_group (signal = signup-question field populated). Honours [[outside-only-data]] + [[scrape-privacy-default]] (no member identifiers). Outside-report T0 surface (directory.ascendalliance.com.au/group/<slug> \"Email me the full PDF\" CTA): adds surface-level honest_attest_checkbox (self-declared owner/admin \u2014 anti-abuse + greenlight alignment, NOT a lawful-basis substitute; operation-level gate stays none). PDF generated from public /about + /plans (DC-06), stored in R2 private bucket OUTSIDE_REPORTS_R2 (Cloudflare sub-processor, already disclosed \u2014 object storage purpose added to sub_processors.Cloudflare), fetched only via signed-URL /r/<token>, 24h expiry, HMAC-bound to request_id, 410 Gone + debounced (1/d/email/slug) re-issue on replay. Rate-limit floor: 3/h/IP (CF-Connecting-IP), 10/d/email, 50/d/slug. NOT a publish event (skoolcore.publish_gate not on path \u2014 PDF private to requester). Point-of-collection notice rendered inline on form (Art 13). Earlier draft name \"outside_report_lead_capture\" REJECTED as a separate OP \u2014 absorbed here per REVIEW-compliance-OP-15-wordings-2026-06-24.md \u00a71.\n","requires_dpa":false},{"data_categories":["DC-01","DC-05"],"description":"Generate T1 (insider/admin data) self-service report for DPA-signed owners via extension drawer. Inputs: cookied admin dashboard via SkoolClient (DC-01 owner_group_aggregate). Owner identity DC-05. Delivered via signed-URL email to DPA signer_email.","gate":"own_group_or_dpa_signed","id":"OP-16","name":"report_generation_t1","notes":"Lane B (DPA member processing). Lawful basis = contract (Art 6(1)(b), DPA v2.5+). Sub-processors: GHL (US) for email delivery; Supabase EU for audit_log row. Retention_ref: audit_log (24mo rolling, 90d email redaction). Layer-2 worker re-checks DPA via require_dpa(slug, soft_fail=False). Email body carries Controller-reminder boilerplate per REVIEW cc0a82b C-e1. BCC privacy@ascendalliance.com.au per C-e2. Signed-URL delivery (revoke-aware) per C-e3.\n","requires_dpa":true},{"data_categories":["DC-05"],"description":"Internal accountability log of T0/T1 report generations. Append-only, 24-month rolling window with automated purge, requester email redacted to SHA-256 after 90 days. Not customer-facing.","gate":"none","id":"OP-17","name":"audit_log_retention","notes":"Internal infrastructure. Purpose: Art 28(3)(h) DPA accountability + Art 30 ROPA evidence of processing activities. Retention_ref: audit_log. 24mo rolling cap matches ext_telemetry + ext_dpa_ack precedent. 90d email-redaction window per REVIEW cc0a82b C-c2 \u2014 once active dispute window closes, only SHA-256 hash retained for accountability. Automated via Supabase scheduled function nullify_old_emails() + purge_old_audit_log().\n","requires_dpa":false},{"data_categories":["DC-05"],"description":"Deliver generated reports + welcome/onboarding emails via existing GHL transactional email sub-processor relationship. Bridge sender for OP-15 + OP-16. No new sub-processor disclosure required.","gate":"none","id":"OP-18","name":"email_transactional_ghl","notes":"Lane B + Lane C bridge. Path C per REVIEW cc0a82b Q-d-2 (chosen by John 2026-06-24 over SES Path B + CF Email Path A). Sub-processor: GoHighLevel (US, already disclosed under \"GoHighLevel CRM contact data\" purpose since v1.4 \u2014 transactional email is within disclosed scope). No DPA bump. No 30-day sub-processor notice required. Same lawful basis as the originating OP (consent/LI for OP-15, contract for OP-16). Footer auto-inject: viral_kit \"Powered by Leverage Lab\" + back-link + privacy@ contact (per [[viral-by-default]] + [[footer-entity-disclosure]]).\n","requires_dpa":false},{"data_categories":["DC-05","DC-07","DC-08"],"description":"User-initiated bug/debug self-report from the extension. Owner transmits their OWN diagnostic context (ext_version, install_id, own-group slug + own role + own DPA state, own Skool handle (owner_handle, DC-05) + own Skool user id (owner_id, DC-08) so John knows WHO reported, last <=5 already-scrubbed extension error-buffer entries, own collect-status counts, user_agent/platform) plus a free-text note. owner_handle/owner_id are the owner's OWN self-identity (the same self.id + handle the DPA sign flow resolves) \u2014 owner-own, never member data or any other person; best-effort (null when not on a Skool page). PII-scrubbed server-side. Fires pre-DPA so a stuck/unsigned owner can still report. Route POST /api/bug-report -> ext_bug_reports. No member data, no cross-group data, no tokens.","gate":"none","id":"OP-19","name":"ext_bug_report","notes":"Legitimate interest, owner-own diagnostic self-report, PII-scrubbed server-side. owner_handle (DC-05 Skool handle) + owner_id (DC-08 owner/admin own Skool user id) added patch66 \u2014 the owner's OWN identity in their OWN support request so John knows who reported; NOT member data, NOT any other person; best-effort, null when unresolvable. Retention_ref: ext_bug_report (1y rolling). Sibling of OP-08 ext_uninstall_feedback + OP-09 ext_owner_pain_inbox. No member data, no cross-group data, no tokens; fires pre-DPA (exempt) so a stuck/unsigned owner can still report.","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-01","DC-06"],"description":"Serve an anonymised cross-tenant benchmark percentile: given a cohort key (size_band x price_band x category x free_paid_model) + a metric axis, return the owner group aggregate percentile position + month-on-month delta within a k>=5-gated cohort cell. Read-gate REFUSES any cell with <5 distinct contributors (honest-null, never a number). Pool rows are identity-detached at write time (no slug/name/install_id; CHECK constraint bans identifier columns) = Recital 26 non-personal aggregate. Provenance split: t1_consented (signed-owner aggregates, already collected under the DPA) and t0_public_estimate (public /about + /plans, DC-06) are NEVER blended in one cohort cell; the public segment is stamped est. No member-level data, no named group on any surface (chip climb-language only; no fail-shame). GET /pool/percentile?cohort=<key>&axis=<a>.","gate":"k_anon_5","id":"OP-20","name":"serve_pool_percentile","notes":"Lane Public / anonymised benchmark. Lawful basis = legitimate interest (Art 6(1)(f)); the served aggregate is Recital-26 non-personal once identity is detached at write time. T1 pool INCLUSION is already authorised by dpa.anonymised_sharing_permitted (identity-stripped aggregated data may be shared in benchmarks at the Processor discretion) + dpa.revocation_anonymisation_clarification (withdrawal destroys the salt -> row non-personal). NO new data category (DC-01 already-collected owner-group aggregate + DC-06 public), NO new sub-processor (worker + Supabase AU already disclosed), NO member-level data, NO new host permission. k>=5 read-gate is the STRUCTURAL answer to the COMPLIANCE-TRIGGERS HALT trigger Cross-tenant aggregate where k<5 \u2014 a cell that fails k>=5 (incl. after axis-relaxation fallback) returns honest-null, never a number. Sibling of OP-12 serve_anonymous_group_score (Lane Public serve). Surfaces: ext percentile chip (own group vs anonymised cohort, counts-only) + Wednesday own-group aggregate post (PRIVATE-by-default, John approves each, HD-07) + quarterly State-of-Skool artefact (seam only, k>=5 + no slugs + John-approved) + owner-initiated PUBLIC share card (own metric vs anonymised k>=5 cohort; Mode A download/clipboard, owner-click = consent, fail-closed honest_null omits the line; shipped leverage-lab-signal v0.20.150 / commit 817bb7d; ref REVIEW-compliance-sharecard-percentile-2026-07-23.md C5, disclosure NONE). No DPA version bump (serve of anonymised aggregate, no member-data posture change). See PLAN-state-of-skool-pool-2026-07-19.md + REVIEW-compliance-pool-p1-clearance-2026-07-19.md. Schema (identity-detach CHECK constraints + k-anon table shapes): supabase_schema_pool.sql.","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-06","DC-10"],"description":"Before/after ad-readiness receipt tool (apps/skool-traffic-finder, served at about.mrrmax.com). Captures a PUBLIC Skool group /about page (DC-06) at two points, scores ad-readiness, renders a private before/after receipt for the signed-in user. Re-homed inside the Leverage Lab gateway shared Google session (SSO handoff, \u00a75) \u2014 replaces the CF-Access email-OTP lock. Rows keyed by operator DC-10 (experiments.uid = Google sub); every read AND uid = ? (404-not-403). Screenshots in R2 (private, uid-namespaced keys, ownership-checked serve).","gate":"gateway_session","id":"OP-21","name":"before_after_receipt_tool","notes":"Tier 1 \ud83d\udfe2: PUBLIC /about only (DC-06, no member identifiers, no cookie) keyed to operator own identity (DC-10). Zero-inside enforced end-to-end (box _assert_no_inside + tier always public; MRR/retention/join-rate locked behind a future owner-DPA phase, never in this op). USER auth = gateway shared Google session via SSO handoff (host-scoped mrr_tool_session cookie; NO parent .mrrmax.com cookie \u2014 Compliance-gated); no per-tool OAuth. Capture-engine box auth SEPARATE + unchanged (CF Access service token + body HMAC). Lawful basis: legitimate interest for public /about (Recital 30 B2B, as OP-01/OP-12) + contract for operator identity (the sign-in). No new sub-processor (Worker + R2 already disclosed). No DPA version bump (no member-level processing). Migration 0004_experiments_uid.sql. Ref PLAN-about-tracker-gateway-auth-2026-07-22.md.","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-12"],"description":"Collect and hold the opaque group-creator key (DC-12) for INTERNAL de-duplication of published denominators and cohort correctness. Refresh-in-place local store only (snapshots/panel/owner-keys.json, gitignored). The owner-portfolio GRAPH itself \u2014 person nodes, portfolio render, badge chips, any /@handle portfolio assembly \u2014 is NOT BUILT and is NOT authorised by this row.","gate":"local_only_no_publish","id":"OP-22","name":"owner_portfolio_graph_internal","notes":"Lane D, sole Controller (Tinker Electric Pty Ltd), Art 6(1)(f).\n\nNARROWED at registration. REVIEW-compliance-owner-graph-2026-07-26.md 10\nqueued this operation as the owner-portfolio GRAPH (variant a/b) over\n[DC-12, DC-06, DC-01]. That graph was NOT built \u2014 John took the\ngroup-centric route instead (SPEC-compliance-public-community-graph\n2026-07-26 10). Registering the graph as a live operation would imply\nprocessing that is not happening, which is the exact error the v46\naccuracy corrections narrowed on OP-12/OP-13. This row therefore covers\nONLY what actually runs: collection + refresh-in-place holding of the\nopaque key. The DC-06/DC-01 group metadata arriving in the same fetch stays\nunder OP-01 (scrape_anonymous_about_page) \u2014 not duplicated here.\n\nBINDING CONDITIONS that carry from the owner-graph verdict and remain\nunmet, so the graph stays unbuilt: P-8 person-keyed suppression list (does\nnot exist), P-11 written LIA incl. the named-node vs opaque-key necessity\nanswer (does not exist), P-9 /privacy Lane D owner-portfolio wording (not\nwritten). Any render, publication or third-party exposure of the key or of\ngroups grouped by it re-enters Compliance as a fresh HALT.\n\nStructural controls in force: anonymous lane only (P-4, no cookie ever);\nmembership hard-denied at the parse boundary (P-1, HD-09); private and\nzero-member groups excluded from every derived surface; no publish_gate\npath (P-7); no dated person-keyed series (P-12) \u2014 the store is overwritten,\nnot appended. Retention key owner_graph.\n\nAuthority: REVIEW-compliance-owner-graph-2026-07-26.md, as narrowed by\nREVIEW-compliance-registry-v47-2026-07-26.md.\n","requires_dpa":false},{"cross_group":"allowed","data_categories":["DC-06","DC-01"],"description":"Publish GROUP-LEVEL derived cohort placement on the existing Lane D public directory: cohort percentile (category x size_band x membership_model ladder) plus group-keyed band-movement, k>=5 gated, rendered into the per-group pages already published under OP-14. Group nodes and group-attribute derivations only. No person node, no same-operator edge, no top-N page, no compare-two surface, no estimated MRR as a visual dimension.","gate":"k_anon_5","id":"OP-23","name":"publish_group_cohort_map","notes":"Lane D, sole Controller (Tinker Electric Pty Ltd). Lawful basis Art 6(1)(f)\nINHERITED from OP-14 \u2014 the unit of analysis stays group-centric so the\npurpose is compatible under Art 5(1)(b), which makes this an LIA AMENDMENT,\nnot a fresh LIA. New OP rather than an OP-14 amendment because the GATE\ndiffers (none -> k_anon_5).\n\nk>=5 HONEST-NULL, OP-20 floor reused verbatim, implemented as a cohort\nLADDER (category x size_band x membership_model -> category x size_band ->\nsize_band). The narrowest cell clearing k>=5 wins and the level used is\nstamped on the output. If no level clears, the percentile is null and the\npage says the group cannot be placed \u2014 never a number, never a rounded\nnumber, never a tilde. T0-public and T1-consented segments are NEVER\nblended in one cell; a T1-marked input row ABORTS the build rather than\nsilently blending. Public segment stamped est.\n\nGroup-keyed only (spec P-12): no owner field is read or emitted. Private\n(privacy == 1), zero-member and objection-blocklisted slugs are excluded\nfrom cells AND from every derived count (Art 21 cascade). Estimated MRR is\nnever computed, ranked or emitted on this path, so it cannot become a node\nsize or colour downstream. Climb-language only; no rank ladder, no\nfail-shame (Rulings 8.3 / 8.4). Movement carries a MANDATORY coverage stamp\nand stays honest-null until the fixed panel has >=2 observation days.\n\nCode symbols: apps/scoreboard-v2/cohort_stats.py K_ANON (the k>=5 floor),\nassert_single_segment (no T0/T1 blend), eligible (private / zero-member /\nblocklist exclusion at the publish boundary).\n\nRetention: reuses the existing group_aggregate key \u2014 no new retention key\nfor this operation. No new data category. No new sub-processor. No new\nhost permission. DPA current_version unchanged at 2.6; disclosure class\nNONE; no member notice owed (no members processed).\n\nSEQUENCING NOTE (recorded, not excused): the derivation and the rendered\nper-group blocks shipped in 9716eeb / b82242f BEFORE this row landed,\nagainst the spec instruction that build begins after the registry rows.\nThe surface itself sits inside the cleared envelope (spec S2/S3 = green)\nand the gap closes with v47.\n\nAuthority: SPEC-compliance-public-community-graph-2026-07-26.md 3, 6, 8;\nREVIEW-compliance-pool-p1-clearance-2026-07-19.md (OP-20 k>=5);\nREVIEW-compliance-public-deploy-directory-2026-06-23.md Rulings 8.1-8.4;\nREVIEW-compliance-registry-v47-2026-07-26.md.\n","requires_dpa":false},{"data_categories":["DC-05"],"description":"Per-recipient click attribution on John's OWN link-tracker (go.mrrmax.com, apps/link-tracker) for the Watchtower CTA embedded in T1 growth reports. The report emits an opaque, deterministic per-recipient code (r = sha256(salt + report group slug), 12 hex chars) plus a non-identifying placement code (p) on the tracker hop ONLY. Both are logged on the tracker's own D1 clicks row and STRIPPED before the outbound 302 \u2014 the destination (skool.com/<group>/plans) receives no identity params, only generic campaign UTMs. Replaces the previous build's utm_content=<group slug>, which leaked recipient group identity through to skool.com.","gate":"none","id":"OP-24","name":"report_cta_click_attribution","notes":"Lane C (john_marketing_leadgen). Lawful basis Art 6(1)(f) legitimate interest \u2014 response measurement on John's own marketing to a B2B lead he already has a relationship with (Recital 47 direct-marketing interest, Recital 30 B2B). Direct sibling of OP-10 ext_referral_chain: a short opaque code in a URL, never carrying a Skool user id, resolved only by John locally. NO new data category (DC-05 pseudonymous code). NO new retention key \u2014 reuses retention.ext_referral_code (indefinite for attribution chain). NO new sub-processor (Cloudflare Worker + D1 already disclosed). NO member-level data, NO cross-group data, NO consent_log touch, NO publish_gate path. No cookie set on the redirect path, so no PECR/ePrivacy consent trigger; click rows keep the existing PII-minimal shape (truncated IP, referrer host only, device class only, coarse country). DPA current_version unchanged at 2.6; disclosure class NONE; no member notice owed (no members processed). BINDING CONDITIONS (test-enforced): C-1 outbound redirect carries no r/p/utm_content/identity-shaped param (ATTRIBUTION_PARAMS + IDENTITY_PARAMS strip in link-tracker.mjs::mergePassthrough); C-2 recipient code opaque + deterministic, never a raw slug/email/handle (generate.py::_recipient_token); C-3 attribution params charset+length validated (^[A-Za-z0-9_-]{1,32}$) before storage, rejected values log NULL not raw; C-4 per-recipient click data stays private to John's own tenant (AND creator_id = ? on every read), never published, never sold; C-5 no per-person behavioural profile built from clicks \u2014 that re-enters Compliance as a fresh HALT. Authority: outreach/REVIEW-compliance-watchtower-click-attribution-2026-07-26.md.","requires_dpa":false},{"data_categories":["DC-07"],"description":"Per-install onboarding milestone counts for the signal extension: E1 gate shown, Connect tapped, gateway hub arrived, Google authed, bearer received by the ext, first submit, E3 report rendered, update-welcome shown. Counts only, keyed on the anonymous per-install install_id, POSTed to the EXISTING /api/ext-telemetry route and stored in the EXISTING ext_telemetry table. Measures the owner-operator own journey through our own product so drop-off between install and first report is visible. NO member data, NO Skool content, NO cross-group data, NO per-person profiling.","gate":"own_install","id":"OP-25","name":"ext_onboarding_funnel","notes":"Lane C (owner-journey product analytics). Lawful basis Art 6(1)(f) legitimate interest - product-funnel measurement on our own installers, the operator acting on their own house (Recital 30 B2B). Direct sibling of OP-08 ext_uninstall_feedback: an anonymous per-install id and an event name, nothing else. NO new data category (DC-07 install_telemetry already covers install_id plus panel engagement counts). NO new retention key - reuses retention.ext_telemetry (2 years rolling, automated purge). NO new sub-processor (Cloudflare Worker plus Supabase already disclosed). NO new endpoint (existing POST /api/ext-telemetry). NO new host permission (ascendalliance.com.au already in the ext manifest). NO consent_log touch, NO publish_gate path, NO member-level data, NO cross-group data. DPA current_version unchanged; disclosure class NONE; no member notice owed (no members processed). BINDING CONDITIONS (test-enforced): C-1 payload whitelist-strict - keys are a subset of install_id, event, ts, event_id, slug, meta, and meta keys are a subset of ext_version, ms_since_prev; any other key fails the test. C-2 worker EXT_TELEMETRY_ALLOWED_EVENTS and client PRE_DPA_ALLOWED stay in parity for every onb_ literal. C-3 the beacon never carries google_uid, email, name, handle, skool user id or any member_ field, so no new identity correlation is created; the funnel joins on install_id alone. C-4 the gateway fires its two hub beacons server-side fire-and-forget and persists nothing - install_id is never stored next to google_sub in gateway D1. C-5 no per-person behavioural profile is built from these events; that re-enters Compliance as a fresh HALT. Plan: outreach/PLAN-funnel-telemetry-2026-07-27.md.","requires_dpa":false},{"data_categories":["DC-13","DC-05"],"description":"Produce an owner-requested YouTube -> Skool funnel audit of the RECIPIENT'S OWN public YouTube channel and deliver it privately to that same person. Inputs: public YouTube metadata about the recipient's own channel (DC-13) + recipient identity (DC-05) + an anonymous-lane Skool /about dead-slug check on the slugs the channel itself links to. Output is a single private deliverable, expiry-bound, addressed to the subject.","gate":"own_group_or_dpa_signed","id":"OP-26","name":"funnel_audit_owner_channel","notes":"Lane A (ll_service_customer). Lawful basis = contract, Art 6(1)(b) \u2014 the\naudit is the service the customer asked for. Subject == recipient == channel\nowner. Sub-processors: Google (US, already disclosed) as the source\nplatform; Cloudflare (already disclosed) for private delivery. Retention_ref:\naudit_log. Skool I/O only via skoolcore.SkoolClient on the anonymous lane\nwith the identified UA (C-09 / C-10); endpoints group_about_public +\ngroup_meta_api2_anon are already registered in data-source-registry.yaml\n(C-14 satisfied \u2014 any NEW Skool path re-enters registration BEFORE the fetch\nships).\n\nRATIONALE FOR A DEDICATED OP (not an OP-16 stretch). OP-16's registry text is\nexplicitly a cookied-Skool-admin report (\"Inputs: cookied admin dashboard via\nSkoolClient (DC-01)\"). tools/yt_deep_scan already stretched it with a semantic\nnote and Compliance let that ride for a LOCAL, never-published tool.\nStretching the same words to cover a PUBLISHED deliverable sourced from a\nDIFFERENT platform would make the registry text mean nothing.\n\nBINDING CONDITION \u2014 RECIPIENT GATE, FAIL-CLOSED (compliance condition C2).\nA config string is not a gate. Before render AND again before publish, the\npipeline must resolve the recipient against machine-checkable state that\nalready exists, and REFUSE on anything less:\n  (1) tools/dpa_status_query.py --slug <recipient_slug> returns\n      signed=True, revoked=False \u2014 the canonical reader (worker\n      /api/dpa/status + consent_log). NEVER outreach/gdpr-register.md,\n      which is derived and is itself a HALT trigger; OR\n  (2) a recorded request from that subject (the ll-members.csv\n      signup-question pattern), captured as a STRUCTURED field in the run\n      config carrying its source \u2014 not prose.\nPlus, unconditionally: the skool.paid / skool.free slugs in the run config\nmust be the same slugs the scan actually found in that channel's own\ndescriptions. If the channel points at a group the recipient does not hold,\nthe gate FAILS \u2014 that is the \"am I auditing the right person's funnel\" check\nand it is free, the scan already extracted the links.\nNo soft-fail path, no --force, no self-attestation checkbox. An\nhonest-attestation checkbox is anti-abuse only and is NOT a lawful-basis\nsubstitute (the OP-15 ruling); do not rebuild it and call it a gate.\ntests/test_yt_funnel_recipient_gate.py asserts REFUSAL: unsigned DPA ->\nrefuse; revoked -> refuse; recipient slug absent from the scanned links ->\nrefuse; missing or empty recipient block -> refuse.\n\nBINDING CONDITION \u2014 EXPIRY + REVOKE BY DEFAULT (compliance condition C4).\nEvery mint sets a non-null expires_at; default 90 days, config-overridable\nDOWNWARD only. Revoke path documented in the delivery stub. Asserted in\ntests/test_yt_funnel_publish_private.py alongside the existing public=False /\naudience='private' assertions. Fixes defect B-2 on precedent id=92 (a\ncapability URL naming a person and their traffic numbers must not live\nforever by default).\n\nBINDING CONDITION \u2014 ONE SUBJECT PER ARTEFACT. More than one channel's data in\na single artefact is a cross-owner join -> C-02 HALT. Each new subject is a\nnew data subject and runs only after passing the gate above (compliance\ncondition C3).\n\nBINDING CONDITION \u2014 DC-09 ISOLATION IS STRUCTURAL (compliance condition C6).\nyt_funnel_* must not read or write member_qualify_cache.json, the leak\ncensus, or any qualify-grid artefact. AST-asserted, not commented.\n\nOPEN, NOT RESOLVED HERE \u2014 YOUTUBE FETCH METHOD (compliance condition C5,\nJohn's call, on the record): Data API vs yt-dlp vs hybrid, written up in\noutreach/DISCUSSION-compliance-yt-fetch-method-2026-07-27.md before the\nscanner ships. Not a GDPR question; a platform-terms + posture question\n(C-10, aligned-with-skool-posture). Non-negotiable regardless of the pick:\nNO player_client=web_embedded framed as a \"429 workaround\" \u2014 back off, never\nroute around a limiter. Concurrency default 2, ceiling 4.\n\nDPA IMPACT. Disclosure class MINOR, no dpa.current_version bump, no re-sign,\nno member notice owed, no new sub-processor, no consent_log write (the\nconsent_log is READ by the gate, never written by this operation).\n\nAuthority: outreach/REVIEW-compliance-yt-audit-pipeline-2026-07-27.md\ncondition C1.\n","requires_dpa":true}],"publish_rules":{"amber_on":["named individual content (words attributed to a person)","sentiment/intent reading on named comments"],"consent_tokens":{"description":"Consent must be explicit, per-report, and revocable","storage":"outreach/consent-log.json"},"red_on":["named teardown of a stranger's group with no consent","cross-group member profiling","publishing member verbatim content"]},"retention":{"audit_log":{"notes":"T0/T1 report-generation audit trail (OP-17). 24mo rolling \u2014 automated purge via Supabase scheduled function purge_old_audit_log(). Requester email field redacted to SHA-256 hash at 90 days via scheduled function nullify_old_emails() per REVIEW cc0a82b C-c2 (Art 5(1)(c) data-minimisation). Hash field requestor_email_hash retained for full 24mo window. Migration patch44_audit_log.sql.","value":"2 years rolling"},"erasure_response":{"notes":"Article 12 GDPR. No hard SLA in days \u2014 slackest defensible posture.","value":"without undue delay"},"ext_admin_group":{"notes":"Owner's admin group membership record (Lane A). Deleted on DPA termination.","value":"for the term of this DPA"},"ext_bug_report":{"notes":"Bug-report self-reports (OP-19, DC-05+DC-07+DC-08). 1y rolling window \u2014 automated purge, matches ext_owner_pain_inbox (owner free-text). owner_handle (DC-05) + owner_id (DC-08) = owner-own identity, same 1y window.","value":"1 year rolling"},"ext_dpa_ack":{"notes":"DPA-update click-to-ack rows (OP-11). 2y rolling window \u2014 automated purge.","value":"2 years rolling"},"ext_install":{"notes":"Extension install record (Lane A). Deleted on DPA termination.","value":"for the term of this DPA"},"ext_message":{"notes":"Owner\u2192owner messages via extension (Lane A). Deleted on DPA termination.","value":"for the term of this DPA"},"ext_pain":{"notes":"Owner-authored pain inbox entries (OP-09, HD-08). 1y rolling window \u2014 automated purge. Bundle 2026-06-19 M6 audited retention.","value":"1 year rolling"},"ext_referral_code":{"notes":"Short 6-char ref_code (OP-10). Indefinite for attribution continuity; non-personal standalone.","value":"indefinite for attribution chain"},"ext_sprint_signal":{"notes":"Sprint engagement signals (Lane A). Deleted on DPA termination.","value":"for the term of this DPA"},"ext_t0_telemetry":{"notes":"Anonymous T0 telemetry from extension Phase 2. install_token_hash is SHA-256 trunc-24 of opaque token (no identity binding). Retention period stands as the cleared policy, but NOTE 2026-07-26 (registry v46): the table is NOT APPLIED in production and OP-13 is DORMANT (never live, zero rows), so this period is not currently running against any data. See supabase_schema_ext_t0_telemetry.sql.","value":"2 years rolling"},"ext_telemetry":{"notes":"Panel engagement events (OP-07). 2y rolling window \u2014 automated purge. Bundle 2026-06-19 M6 audited retention.","value":"2 years rolling"},"ext_uninstall":{"notes":"Uninstall pings (OP-08). 2y rolling window \u2014 NOT indefinite.","value":"2 years rolling"},"ext_user":{"notes":"Extension user identity (Lane A). Deleted on DPA termination.","value":"for the term of this DPA"},"ghl_contact":{"notes":"Lane A owner contact + Watchtower active-billing data retained while subscription active; +6mo grace after final cancellation (matches Lane A floor); +7y for invoice/tax records per AU Income Tax Assessment Act. Watchtower auto-roll continuity = ongoing legitimate processing under DPA scope until cancellation. Quarterly purge cadence runs post-grace.","value":"active billing cycles + 6 months grace post-cancellation + 7 years for invoice/tax records"},"group_aggregate":{"notes":"Group-level aggregates are not personal data and may be retained for benchmarks.","value":"indefinite for trend continuity"},"member_level_dpa":{"notes":"Per-DPA term, deleted on termination.","value":"for the term of this DPA"},"member_level_non_dpa":{"notes":"Without a DPA, member-level data is not collected \u2014 so there is nothing to retain.","value":"purged regularly"},"owner_graph":{"notes":"Opaque group-creator key store (DC-12 / OP-22), snapshots/panel/owner-keys.json. OVERWRITTEN in place on every run \u2014 no dated history of a person key exists or may be created (spec P-12). Gitignored, local, never published. Purged for an entity on objection. GROUP-level series are unaffected and stay on retention.group_aggregate \u2014 they are group-keyed, not person-keyed. NOT a Lane A/Lane B customer retention window: no owner or member contracted-for data retention period changes, so this key is registry-MINOR with DPA disclosure class NONE.","value":"refresh-in-place, no longitudinal person series; cache purged on objection"}},"rights":[{"art":"Art 17 GDPR","id":"R-01","mechanisms":["HMAC-signed footer link on any report we publish for you","Opt-out form at the privacy contact page","Extension per-group Revoke control (Settings \u2192 Manage DPA)","Email privacy@ascendalliance.com.au"],"name":"erasure","notes":"None of the four mechanisms is exclusive.","sla":"without undue delay"},{"art":"Art 15 GDPR","id":"R-02","mechanisms":["Email privacy@ascendalliance.com.au"],"name":"access","sla":"without undue delay"},{"art":"Art 20 GDPR","id":"R-03","mechanisms":["JSON export via extension Settings \u2192 Export"],"name":"portability","sla":"without undue delay"},{"art":"Art 21 GDPR","id":"R-04","mechanisms":["Opt-out form","Email privacy@ascendalliance.com.au"],"name":"objection_opt_out","sla":"without undue delay"}],"sub_processors":[{"country":"AU","country_note":"AU primary, US available","dpa":"see provider DPA","name":"Supabase","purpose":"Database, hosted AU primary"},{"country":"Global","country_note":"Regions across AU, EU, and US","dpa":"see provider DPA","name":"Cloudflare","purpose":"Worker compute, CDN, edge network"},{"country":"US","dpa":"see provider DPA","name":"Skool","purpose":"Source platform \u2014 owner-authorised data extraction"},{"country":"US","dpa":"see provider DPA","name":"GoHighLevel","purpose":"CRM contact data for the Customer: name, email, Skool handle, products purchased, products of interest, and service-usage telemetry (e.g. extension activity, report runs)"},{"country":"US","dpa":"see provider DPA","name":"Anthropic","purpose":"LLM API for diagnostic generation (aggregate, non-identified inputs)"},{"country":"US","dpa":"see provider DPA","name":"OpenAI","purpose":"LLM API for diagnostic generation (aggregate, non-identified inputs, alternate)"},{"country":"US","dpa":"see provider DPA","name":"Google","notes":"No-train data policy via Google Workspace enterprise agreement.","purpose":"AI-assisted features via Google AI / Google Workspace Gemini; no-train policy applies via Workspace agreement. No member-level verbatim content is sent."},{"country":"AU","country_note":"Self-hosted on Cloudflare-fronted infrastructure (dashboard.ascendalliance.com.au)","dpa":"see provider DPA / self-hosted under LL operational control","name":"Metabase","purpose":"Analytics dashboard rendering the Customer's OWN-GROUP aggregate metrics (counts, percentages, time-series, AAEMR, discovery rank). Owner-safe aggregate views only \u2014 no member-level PII, no cross-group, no sprint/leak data. Surfaced to the owner via a short-TTL, group-locked, signed embed (10-min JWT; access gated by signed DPA)."}],"website_analytics":[{"country":"US","name":"Google Analytics","notes":"Store-listing marketing analytics, controller-role (our own website). Anonymous store-page visitors only \u2014 never owner/member data, never the extension. Not an Art-28 member-data sub-processor; disclosed here as a privacy-policy transparency item. See provider privacy terms.","provider":"Google LLC","purpose":"Web page-view analytics for our PUBLIC Chrome Web Store listing page ONLY \u2014 measures anonymous visits to that store page. NOT used inside the extension: in-extension usage is measured by our own first-party telemetry (Supabase). Google Analytics never sees owner or member data from the extension; it only sees anonymous visitors to the public store listing.","scope":"Our PUBLIC Chrome Web Store listing page ONLY"}]}