Lechon Liga

Privacy notice

Version: privacy-notice-2026-08 · first published 2026-08-27

Superseded on counsel review.

This notice is a factual description of what the Lechon Liga database and services actually store, written by the engineering team from the schema and the code. It is not legal advice and it has not been reviewed by counsel. When counsel reviews it, the reviewed document replaces this one under a new version string, and that version string is how you tell them apart.

Buod (Tagalog)

Ito ang listahan ng lahat ng iniimbak ng Lechon Liga tungkol sa isang tao: kung ano, bakit, gaano katagal, at sino ang nakakabasa. Hindi naka-imbak nang buo ang numero mo ng telepono sa mga table ng produktong ito — keyed hash lang, may lihim na pepper; hawak ng Supabase Auth ang buong numero para maipadala ang one-time code, at naaalis iyon kapag binura ang account. Hashed din ang IP address; hindi kami nag-iimbak ng buong IP. Walang analytics, walang tracking script, at walang third-party na script sa alinman sa mga page namin. Kayang burahin ang account mula mismo sa app (Privacy at pahintulot → Burahin ang account ko): naaalis ang personal na datos, pero nananatili ang ledger sa ilalim ng isang tombstone id na hindi na tumuturo sa iyo. Ang email sa waitlist ay wala pang self-serve na pagbura — kailangan itong hilingin sa contact address sa landing page, at wala pa kaming naitatalagang address hanggang ngayon; nakasulat iyon nang buo sa ibaba.

English is the reference text of this notice. A full Tagalog rendition follows counsel review.

1. What this document is, and what it is not

This notice describes behaviour that already exists in code: every table listed below is a table in the shipped database schema, and every retention and access statement is derived from that schema, its row-level security policies and its column grants.

Player Terms, advertiser terms and contest rules are not written yet. If you came here looking for them, they do not exist on this site and nothing on this site pretends they do. They are with counsel, because they are instruments that bind rather than documents that describe, and drafting them is not an engineering task. The two documents that do exist are this notice and the Lechon Liga Talent terms (talent-terms-2026-08).

2. Who this is about — the three personas

Three kinds of person use Lechon Liga, they sign in through three different doors, and the database holds different things about each of them.

A fourth role exists internally: an admin is an operator of this company, listed in a table the product never publishes, who reviews campaigns and briefs. An admin is one of us, not a persona this notice is written for.

3. What each table stores, why, and who can read it

The default is deny. Every table has row-level security switched on, clients hold explicit per-column SELECT grants rather than table-wide ones, and no client of any kind can write to a money table — every balance-moving write goes through a server-side function that only the API service can call. "Own row" below means row-level security limits the read to rows whose user id is the caller's own.

3.1 The player

Tables written when a person uses the mobile app.
TableWhat it holdsWhyWho can read itRetention
auth.users The sign-in identity: a phone number in E.164 form, or an email address, held by Supabase Auth. It is what a sign-in matches against. The account itself, and this company's servers. Until erasure, which nulls the phone number and the email address.
users A keyed hash of the phone number (never the number), a Facebook user id if the account signed in that way, a self-reported city and barangay code, a verification level, an opaque payout-identity reference, a quarantine timestamp, a deletion timestamp, an invite code and the account that referred it. One account per phone; payouts; coarse geography for local derbies; fraud scoring. Own row. Erasure nulls the phone hash, the Facebook id, the payout-identity reference, the city and the barangay, and stamps a deletion time. Four things deliberately survive it: the verification level, the quarantine timestamp, the invite code and the account that referred this one. The anti-abuse reason is in the erased_identity_bindings row further down this table, and section 7.1 lists the whole of what erasure does.
consents One row per purpose — operational, personalised ads, marketing — with whether it is granted, the version consented to and when. It is the record of what you chose. Own row. Held as current state; a change overwrites the row.
consent_events Every grant and every revocation, with the version string and a timestamp. The audit trail behind the row above. Nobody through the client; the API and operators only. Append-only and permanent. The table rejects UPDATE and DELETE, so a consent record can never be corrected — only explained.
pigs The biik: its name, weight, fitness, care streak and cosmetics. It is the game state. Own row. Only the name and the cosmetics are writable by the app. Until erasure, which renames it.
care_events Each pet and each wash, with a timestamp. It is what the daily care cap is counted from. Own row. Kept; no reaper exists.
ad_views One row per completed sponsor video: which network and campaign, the network's own transaction id, a fraud score, a timestamp, a hashed IP address and a device fingerprint. Feed credit is paid once per view, and repeat-abuse scoring needs the view history. Not readable by any client. Kept; no reaper exists. Erasure does not delete these rows — it removes the identifiers that point at a person.
feed_nonces A single-use token id bound to an account, with its issue and expiry times and whether it was redeemed. It binds one reward to one view and stops a replay. Not readable by any client. Deleted after expiry by a reaper that will not delete anything less than a day old and defaults to seven days.
race_entries Which biik ran in which race, the frozen race-day stats the simulation consumed, the finish rank and the prize. It is the race result. Rank and prize are public. The frozen stats are deliberately not published to any client — every entrant would otherwise be able to read every rival's. Kept — it is the race record.
ledger Every movement of in-app balance: the amount, the reason, a reference and the balance after. It is the money record, and it is what the public proof-of-reserves page is computed from. Own rows. Append-only and permanent. Erasure deliberately leaves it intact under a tombstone id, so the accounts still add up while the person is gone.
payouts A withdrawal request: the amount, the rail, its status, the rail's own reference and timestamps. It is the payout state machine. Own rows. Kept as financial history.
erased_identity_bindings Written only at erasure, and only when there was something to write: the tombstone id, the hashed phone, a hash of the payout-identity reference, and whether the account was under quarantine. So that erasing an account is not a way to clear an anti-abuse decision and start again. Nobody through the client. Append-only. It carries no name, no city, no device and no raw number — only hashes, which are useless for identifying or contacting anyone.
deletion_receipts A hash of the confirmation code issued for a deletion request, which channel it came through, and what it found. It is the evidence that a deletion request was carried out. Read through the public status endpoint, which answers only "done" or "unknown" for a code the asker already holds. Kept indefinitely, on purpose. It carries no personal data and no link back to the account; a row that aged out would be an erasure we could no longer prove we performed.

3.2 The advertiser

Tables written when a company buys or funds something.
TableWhat it holdsWhyWho can read itRetention
advertisers Company name, contact person, status. It is who the campaign belongs to. Own row, and admins. Kept while the account exists.
campaigns, briefs What was scheduled or asked for: targeting, budget, dates, the creative reference, the brief text and its prize. It is the product the advertiser bought. The owning advertiser and admins. A brief becomes visible to talents only once it is paid for. Kept as commercial history.
campaign_payments, brief_payments, payment_events The checkout session with the payment provider, its identifiers, the amount, and the raw evidence of every provider webhook. Money must be booked exactly once, and a disputed payment has to be resolvable from durable evidence. No client at all. None of the three carries a column grant, and a database checks that before it checks any row rule, so a query sent from a browser is refused outright. brief_payments and payment_events have no read policy either. campaign_payments does — an own-or-admin rule named campaign_payments_read_own_or_admin, created with the table and never dropped — and the withheld grant is the only thing standing between it and a reader; we name it here rather than leave it out, because somebody auditing this notice against the schema will find it. Payment state reaches the browser only as a campaign or brief status. Kept as financial history.
Storage bucket creatives The uploaded ad video or image. It is what players are shown. The uploading advertiser and admins; served to players through the feed. Kept; there is no delete path, deliberately, so a judged or served asset stays byte-identical.

3.3 The talent

A talent has a users row and a consents record like a player does — that is how a prize can be credited and paid — but never a biik. What is additional is in the Talent terms, section 10, which describes talents, brief_entries and the entry-media bucket in the same table-by-table form as above.

3.4 The waitlist

The waitlist table holds an email address, a referral code minted for it, the code that referred it if any, a confirmation timestamp and the time it was added. It exists so that we can tell people when the app opens. It is invisible to every client: the table has row-level security on with no read policies at all, and the only door into it is one function that takes an address and answers a referral link on a first join or a bare "already joined" on a repeat.

What that does and does not hide. The two answers differ, so submitting an address does still reveal whether an address is already on the list — and we would rather say so here than let this read as a guarantee it is not. What was closed, in 2026-08, is everything else that used to come back on a repeat: the position in the queue, the count of confirmed referrals, and the referral code, which is the credential inside a referral link and could be used to attribute someone else's sign-ups. Removing the last distinction would mean returning no referral code on a genuine join either and mailing the link instead, and this system has no email sender. It is written down as an open decision rather than half-built.

Retention: indefinite. There is no expiry on a waitlist row and no reaper that removes one.

Removal route, stated plainly because it is a gap. There is no self-serve way to leave the waitlist. Account erasure does not touch this table — a waitlist row is not attached to an account. The route is therefore to ask us, at the contact address published in the legal block of the landing page, and no monitored address has been named yet: until one is, the landing page shows no contact line, because an address that bounces is a worse answer than none. Naming one address is the first item on this company's own handoff list, and a self-serve removal is planned for the next database change. Both facts are written here rather than dressed up, because a notice that described a route that does not exist would be the exact failure this document was written to correct.

4. Phone numbers never reach this product's own tables

A phone number reaches Supabase Auth, which needs it to send a one-time code. It never reaches this product's own tables. What is stored is HMAC-SHA256(number, pepper) — a keyed hash, where the pepper is a secret held outside the database, provisioned by an operator, and never committed to the source repository.

The key matters. A plain SHA-256 of a phone number is reversible in minutes: there are only so many Philippine mobile numbers, and anyone can hash all of them. A keyed hash is not, unless the key leaks with it. The function that computes it fails closed: if the pepper is missing it raises an error rather than quietly hashing with an empty key and calling the result a secret. The pepper can be rotated, and the schema records the fingerprint of the pepper in force so a rotation cannot silently orphan every stored hash.

5. IP addresses are hashed, and the cluster signal is switched off

When a sponsor video completes, the API takes the caller's IP address as its own proxy saw it, hashes it with a keyed hash, and stores only the hash. A raw IP address is never written to the database.

The fraud scorer's IP-cluster term — "how many accounts share this address today" — is weighted at zero, deliberately, because carrier-grade NAT is normal in the Philippines and that term punishes households and neighbourhoods for sharing an address. The hash is still stamped on the row, so the signal remains available if a better rule is ever written for it.

6. Device fingerprint

A feed completion has room for a device identifier, and no app we have shipped sends one: the field exists for a future build, and today every view row's copy is empty. If a build does send one, it is stored on the view row — the only place it is kept — and used for one thing: noticing that many accounts are being farmed from one device. The account row holds no device identifier, so erasure nulls none; what erasure removes is the identity on the account, and the view rows survive it, as the ad_views row in section 3.1 says.

7. Deletion

7.1 From inside the app

The mobile app carries the control: Privacy & consent → Delete my account, which arms on the first tap and runs on the second. It calls an authenticated endpoint that can only ever erase the account making the call.

What erasure does, precisely:

It is idempotent: asking twice reports that it was already done rather than pretending to do it again. The account row itself is not hard-deleted, because the ledger references it and the bare id is not personal data once every field beside it is gone.

7.2 Through Facebook

An account that signed in with Facebook can ask Facebook to delete its data. Facebook calls a callback on our API with a signed request; we verify that signature, find the account by its Facebook id, run exactly the same erasure, and return a confirmation code and a status URL — which is what Facebook's data-deletion contract requires. The status URL is public and unauthenticated by design, and it answers only done or unknown for a code the asker already holds.

7.3 The deletion receipt

Every completed request appends a row holding a hash of that confirmation code, the channel it arrived through, and the outcome. It holds no personal data and nothing that links back to the erased account. It is kept indefinitely, because it is the only evidence that the erasure happened.

8. What we do not do

9. Who else sees anything

10. Your rights, and what to do

Under the Philippine Data Privacy Act you can ask what is held about you, ask for it to be corrected, ask for it to be erased, and object to processing. Erasure of an account is self-serve today, in the app, as described in section 7. Everything else — including removal from the waitlist — has to be asked for at the contact address published in the legal block of the landing page, and as section 3.4 says without softening it, that address does not exist yet.

One consequence of the append-only design is worth stating: a consent record and a money row cannot be edited or deleted, by anyone, including us. They can only be added to and explained. That is what makes the public proof-of-reserves page meaningful, and it is a deliberate limit on correction.

11. Changes

This notice is versioned, not dated-and-quietly-edited. The version string above is the identifier; a materially different notice gets a new one. The counsel-reviewed replacement will supersede privacy-notice-2026-08 under its own version string.