Under the hood
How Face Up is built on Kindling
An app that plays its cards face up, and opens any Pool it may read. Here are the three layers for one Pool, the decks and what each gathers, how any other Pool opens under its own name, our two rules and the spec behind the rest, our settings mapped to the spec, the one Pool we host, the block lists we honor, and where v0.1 can’t yet say what we mean.
Three layers, one Pool
Belay Partners, three ways
The same Pool is a page on its host’s own site, a JSON file anyone can read, and a card in any app that reads it. Every Face Up card links back to the other two, and any Pool opens here: this one in the Climbing deck, a seed swap under its own name.
1. The site
Belay Partners is a page on Drip Line’s own site, in its own design, run by its own staff.
Climbers who passed a staff belay check at Drip Line and would like to be asked to climb, most often on Saturday mornings.
2. The data
The manifest is a file at the Pool’s own address §3.1. Any app may read it, and every entry carries the person’s yes.
An excerpt, as the file says it
{
"schema_version": "0.1",
"id": "drip-line-belay",
"name": "Belay Partners",
"intent_tags": [
"climbing",
"belay",
"partners"
],
"visibility": "public",
"consent_model": "vouching-required",
"charter": "Climbers who passed a staff belay check at Drip Line and would like to be asked to climb, most often on Saturday mornings. …",
"curator": [
{
"display_name": "Pilar Echeverría",
"verification_level": "oauth"
},
{
"display_name": "Emil Kowalczyk",
"verification_level": "oauth"
}
],
"entries": "6 entries; one of them:"
}
{
"profile_url": "https://beto-aguilar.carrd.example",
"verification_level": "curator-vouched",
"parsed_profile": {
"display_name": "Roberto “Beto” Aguilar",
"pronouns": "he/him",
"location": {
"city": "Seattle",
"region": "Washington",
"country": "United States"
},
"intent_tags": [
"bouldering",
"climbing",
"belay",
"friendship"
],
"about": "Electrician. At the bouldering wall most Tuesday nights, and glad to catch anyone on a rope on Saturdays. Leads 5.10 and falls off 5.11 cheerfully.",
"messaging_preferences": {
"accept_from": "shared-pool",
"no_cold_messages": false,
"minimum_curator_verification": "email"
}
},
"consent_proof": {
"handshake_id": "hs-drip-line-drip-line-belay-beto",
"accepted_at": "2025-10-15T02:10:00Z",
"method": "curator-vouching"
}
}3. The app
The card Face Up deals from it, in the Climbing deck, as Fatima sees it.
Decks
Shortcuts, and what each gathers
| Deck | Its yes | Pools it gathers | Tags it gathers | Orders |
|---|---|---|---|---|
| Dating | Yes, a date | Face Up: Cascadia Heartwood: Nearby Sundial: Cascadia Card Catalog: Cascadia Many Moons: Harbor Counties Date-Me Docs: Cascadia The Singles Shelf With the address: Queer Harbor: Dating, The Tuesday Table, Second Chapters | dating | Newest here, Nearest, Most in common, Shuffle |
| Climbing partners | Yes, climb together | Belay Partners Tuesday Regulars New to Climbing With the address: Thursday Night: Women and Nonbinary Climbers | climbing, belay, bouldering | Checked first, Newest here, Shuffle |
| The Saturday Run | Yes, run together | The Saturday Run | running | Newest here, Longest running, Shuffle |
| Bandmates and players | Yes, play together | Start a Band: Port Ellery Friday Subs Session Players Horns and Strings for Records | band, sub, session | Newest here, Friday subs first, Shuffle |
| Game night | Yes, share a table | Port Ellery Thursdays Olympia Thursdays Tacoma Thursdays Portland Thursdays Rain Check: Everyone | game-night | Nearest table, Newest here, Shuffle |
| Walking partners | Yes, walk together | Tuesday Morning Walkers | walking | Newest here, Longest walking, Shuffle |
| First-generation mentors | Yes, ask to meet | Midspan Mentors (through its curator) Cape Tarrow First-Gen Alumni | mentorship | Newest here, Nearest, Shuffle |
| Under its own name | The Pool’s own verb, or “Yes, say hello”; “Write a letter” in Correo Lento; none where a Pool is listed for context only | Any Pool no deck gathers: 40 today, listed on the About page. Also the people a deck can’t deal from a Pool it gathers: Many Moons: Harbor Counties, for its members whose own tags don’t say dating. | None needed | Newest here, Longest here, Shuffle |
Every Pool in the library, and where it opens in Face Up, is in the table on our About page.
How a Pool opens under its own name
- Open a Pool from “Open a Pool”, beside the decks, or load a product, or paste an unlisted Pool’s address. The address bar says what is open:
#pool-seed-swap, or#load-board. - Face Up puts each of its Pools in the deck that gathers it. The rest go together into one deck named for what you opened: the Pool, or the product.
- Each card still names its own Pool, a line of its charter and its tags. The yes names the Pool, in its own verb where its charter gives one, and never in a dating word unless the Pool is tagged dating.
- A resting Pool opens and says it is resting. Nobody in it is hidden for it.
Rules
Two rules
These are the only rules Face Up claims. What else a reader owes a Pool is in the spec, cited below, and in each Pool’s own words.
Every card shows the Pool’s name, charter and tags.
Every card, in every deck and under every own name, carries the Pool it came from: its name, one line of its charter and its tags, with its visibility, its curator and links to the Pool on its own site and to its JSON. So does every card on “Yes to you”, and the two cards that turn when you both say yes.No one is shown as open to dating unless their own tags say so.
The Dating deck deals only people whose own intent_tags include dating: the tag itself, not a Pool they are in. So Tamsin and Anouk in Harbor Counties, who are friends first, and Nadia, a matchmaker, are never in it. It holds for you too: if your own tags don’t say dating, the Dating deck deals you nobody and deals you to nobody. The build checks this against the deck the app actually deals, and stops if it fails.
What the spec asks of every reader
- Visibility. An invite-only Pool is never read. An unlisted Pool is read for someone who is in it, or who pastes its address; Drip Line’s Thursday Pool asks members to add it themselves, so we wait for the paste. §3.2
- Messaging and introductions. KindlingDemo.canMessage runs Pool by Pool, with each Pool’s address, its members and its own sender floor, against each person’s own rules, and the card shows every answer in words. consent_model says how people join; where a Pool’s charter or governance says introductions go through a curator or steward (Midspan Mentors, Late Supper’s tables, Hearthhold’s stewards, Seen Work’s Harbor Trades, Meals When It’s Hard), a yes is a request to them. §7
- A Pool’s own words. Correo Lento’s Pools are slow letters, by email, one a week at most, and the co-op holds every first letter until both people say they would like to write: there a yes is “Write a letter”. The Orchard Street Household is listed “so the people we date can see how we are connected”, so Face Up shows it for context only, with no yes. Foul Weather Friends asks apps not to read the Check-In Circle, so we don’t. §3.2
- Pictures. Drawn from each person’s own page by the shared portrait renderer, seeded by their id, and never copied. A page with no picture is dealt as a letter, the way its Pool’s own site shows it. §2.5
Settings
What we chose, and why
| Setting | Value | Spec | Why Face Up chose it |
|---|---|---|---|
| Decks | 7 shortcuts: Dating, Climbing partners, The Saturday Run, Bandmates and players, Game night, Walking partners, First-generation mentors; any other Pool under its own name | §1.2 | UIs are ours to design. A deck is a ready-made shortcut, and never a wall: any Pool we may read opens. |
| Pools read | 60 public today, and 7 unlisted ones only with the address; 3 never | §8.3 | Well-known files are readable without asking. Unlisted Pools we never guess; invite-only Pools we never read; a site that asks us not to read a Pool is left alone. |
| The Dating deck | Only people whose own intent_tags include dating | §2.4 | Rule 2 of the draft proposal: “No one is shown as open to dating unless their own tags say so.” |
| Pool hosted | Face Up: Cascadia, public, tagged dating | §3.2 | One Pool of our own, readable by any app, so the face check has a home. |
| Consent model | vouching-required: the face check, then the handshake | §5.4 | A stricter model may add a step before the handshake and never remove the owner’s no or their leaving. |
| The face check | A curator’s vouch, local to our Pool | §4.4 | It means this face is the person who said yes, in this Pool only. We say so wherever it shows. |
| Handshake window | 14 days, carried in each request’s expires_at | §5.2 | The manifest has no field for it; our governance rules say so too. |
| Verification | On every card, in words and with an icon, as the level in the Pool the card came through | §4.5 | A conforming UI must show it. We add the Pool and its curator. |
| The order | Chosen per deck, each with one honest line | §1.2 | Nobody pays to be higher, and every order is written on the screen. |
| Yeses | App-local; each names its purpose and its Pool; free to see; no limit | §6.3 | v0.1 has no message for a yes, so a yes can’t travel between apps. We say so on the “Yes to you” tab. |
| Two yeses | Both said yes in one deck; counts as mutual interest | §7.2 | For people who take no cold messages, two yeses are the confirmed mutual interest their rule asks for. In Correo Lento they let the first letter go. |
| A request to a curator | An intro message to the curator, through that Pool | §6.3 | Where a Pool’s curator introduces people, the yes goes to them. Nothing goes to the person. |
| Messaging rules | Each person’s accept_from and no_cold_messages; each Pool’s own sender floor | §7.2 | Checked with the shared kit, Pool by Pool, before anything is sent, and the reason shown in words. |
| Identity gating | Unverified senders are held back | §7.1 | Our Pool’s own floor is email. Friday Subs sets its own floor at OAuth, and we honor it. |
| Block lists | Kindling’s public list and The Circular’s | §7.3 | The same two our Pool subscribes to. A personal block is kept on the other person’s identity, in every deck. |
| Leaving a Pool | One tap; a withdrawal message to the curator | §5.3 | The Pool must act within 60 seconds. Ours acts at once. |
| Pictures | Shown from each person’s page, never stored | §2.5 | Portraits here are illustrations of invented people. |
| Noindex | Respected | §2.7 | A page that says kindling-noindex is in no Pool, so it is never in a deck. |
| Messages | Email underneath, a thread in the app | §6.1 | Nobody needs Face Up to answer. |
| Continuity | Two curators; Brody Kaleikini first in line | §9.3 | If the Pool goes quiet for 90 days, members can keep it going. |
| Money | Nothing charged at the connection layer | §1.3 | Face Up nights and an optional page-hosting membership pay for it. |
Our Pool
Face Up: Cascadia
Face Up is for people dating in Cascadia, from Eugene to Vancouver, who like to see a face first. Right if you would like to hear from someone, left if you would not, and when you both go right, you can write. Every face here has been checked by our trust desk against a live selfie, so the person in the photos is the person who said yes. Photos stay on your own page and we show them from there. Nobody pays to be seen, and nobody pays to see who liked them.
The manifest, in plain words
- Address
- https://faceup.example/pools/face-up-cascadia.json
- Visibility
- public
- Consent model
- vouching-required
- Intent
- dating
- Where
- Cascadia
- Sender minimum
- Block lists
- 2, below
- Created
- 6 April 2026
- Updated
- 27 September 2026
- Entries
- 22, each with its consent proof
Curators
- Adaeze Nwosu Signed in with OAuth adaeze@mail.example · primary
- Brody Kaleikini Signed in with OAuth brody@mail.example
Governance
Vouching-required. Before anyone is listed, a member of Face Up’s trust desk compares a live selfie, taken in the app, with the photos on that person’s own page, and vouches for them only if they are the same face. The vouch means that and nothing more: this face belongs to the person who said yes. It says nothing about age, character or intentions, and it is local to this Pool; it carries into no other Pool and no other app. The selfie is deleted as soon as the check is done; we keep only the date and who checked. If the photos on your page change, the desk asks for a new selfie before the new ones show. Anyone may ask the desk to look again, or report a card, at trust@faceup.example. Leaving is one tap, and your card is gone within a minute.
Our discovery file
.well-known/kindling-pool §10.1 §10.2
{
"schema_version": "0.1",
"pools": [
{
"manifest_url": "https://faceup.example/pools/face-up-cascadia.json",
"name": "Face Up: Cascadia",
"intent_tags": [
"dating"
],
"visibility": "public",
"status": "active",
"geographic_scope": {
"region": "Cascadia"
},
"last_updated": "2026-09-27T21:40:00Z"
}
],
"operator": {
"name": "Face Up, an app that plays its cards face up",
"contact": "hello@faceup.example",
"url": "https://faceup.example"
}
}Everyone listed, and who checked them
| Name | City | In this Pool | Listed | Face checked by |
|---|---|---|---|---|
| Keisha Holloway | Tacoma | Vouched for by the curator | 13 April 2026 | Adaeze Nwosu |
| Kwame Asante | Seattle | Vouched for by the curator | 19 April 2026 | Brody Kaleikini |
| Olena Kravets | Portland | Vouched for by the curator | 28 April 2026 | Adaeze Nwosu |
| Fergal Byrne | Vancouver | Vouched for by the curator | 3 May 2026 | Brody Kaleikini |
| Thuy Nguyen | Seattle | Vouched for by the curator | 10 May 2026 | Adaeze Nwosu |
| Maya Lindgren | Seattle | Vouched for by the curator | 20 May 2026 | Brody Kaleikini |
| Tomás Herrera | Portland | Vouched for by the curator | 29 May 2026 | Brody Kaleikini |
| Kalani Akana | Tacoma | Vouched for by the curator | 4 June 2026 | Adaeze Nwosu |
| Hamid Karimi | Vancouver | Vouched for by the curator | 12 June 2026 | Brody Kaleikini |
| Sione Taufa | Tacoma | Vouched for by the curator | 19 June 2026 | Adaeze Nwosu |
| Paloma Quintero | Eugene | Vouched for by the curator | 27 June 2026 | Brody Kaleikini |
| Gabe Morrow | Port Ellery | Vouched for by the curator | 4 July 2026 | Brody Kaleikini |
| Rodrigo Salinas | Salem | Vouched for by the curator | 16 July 2026 | Adaeze Nwosu |
| Jasleen Gill | Vancouver | Vouched for by the curator | 22 July 2026 | Brody Kaleikini |
| Luis Arriaga | Olympia | Vouched for by the curator | 31 July 2026 | Adaeze Nwosu |
| Amara Tesfaye | Seattle | Vouched for by the curator | 8 August 2026 | Brody Kaleikini |
| Liam O’Connell | Port Ellery | Vouched for by the curator | 15 August 2026 | Adaeze Nwosu |
| Itzel Cárdenas | Salem | Vouched for by the curator | 21 August 2026 | Brody Kaleikini |
| Owen Tremblay | Vancouver | Vouched for by the curator | 1 September 2026 | Adaeze Nwosu |
| Astrid Nilsen | Bellingham | Vouched for by the curator | 8 September 2026 | Brody Kaleikini |
| Julian Mercer | Port Ellery | Vouched for by the curator | 17 September 2026 | Adaeze Nwosu |
| Sasha Petrov | Olympia | Vouched for by the curator | 22 September 2026 | Brody Kaleikini |
Discovery
How a deck is dealt
- Read every
.well-known/kindling-poolfile we know of §8.3, and keep every public Pool, resting ones too. Add an unlisted Pool only for someone who is in it or pastes its address §3.2. Never an invite-only one, and never one its site asks apps not to read. - Fetch each manifest, check it against the schema, and check its major version §12.2.
- Drop anyone whose page says noindex §2.7, anyone on a block list we honor §7.3, and anyone you blocked.
- Put each Pool in the deck that gathers it by its tags, and the rest under their own names. For the Dating deck, keep only people whose own tags say dating, and read each one’s own page for age, gender, pictures, prompts and answers. Everywhere else, use the parsed profile in the Pool file, and a page’s job, languages and interests only where the Pool lists that page. Keep none of it.
- For dating, filter by who you’re looking for (and who’s looking for you), age and distance between rough city centers, and never on a field a page leaves blank. Elsewhere, choose which of the deck’s Pools to read. Every filter is free.
- Put them in the order you chose for that deck, with the count of cards left on the screen.
Safety that travels
The block lists we honor
Kindling public block list
Version 3, published 20 September 2026. The list as JSON.
scrapekit/0.3· implementation · scrapingpromo-blast@mail.example· identity · spam
The Circular consortium block list
Version 8, published 27 September 2026. The list as JSON.
listed-them-anyway@mail.example· curator_identity · consent_violationhttps://free-friends-now.example/pools/everyone.json· pool_url · consent_violationbulk-intros@relay.example· identity · spamscrapekit/0.3· implementation · scraping
Sample messages this app sends, validated against the schema: hello-simone-to-tomas.json · withdrawal-keisha.json · hello-fatima-to-beto.json · request-ruben-to-fina.json.
Conformance
A Pool host, a Pool UI and a messaging client
- Compliant Pool host §11.1.A manifest, the handshake, continuity with a co-curator, and a discovery file.
- Compliant Pool UI §11.2.Verification beside every person, the Pool on every card, the §7 layers, withdrawal in one tap, and no silent inclusion.
- Compliant messaging client §11.4.Envelopes that validate, the sender’s level shown, §7.1 gating, and each Pool’s own floor.
Honest limits
What the v0.1 schema can’t say yet
Where the spec text and a schema disagree, our JSON follows the schema.
- A yes has no message (§6.3, §7.2). The spec’s message types are handshake-request, handshake-response, intro and reply; the schema adds withdrawal and system. None says “yes, let’s climb” or “I like you”. So a Face Up yes lives only in Face Up, in one deck: other apps can’t see it, and we can’t see theirs. Two yeses are ours to record, and we treat them as the confirmed mutual interest that
no_cold_messagesasks for. - A request to a curator has no message of its own (§6.3). Where a Pool’s curator introduces people, Face Up sends the curator an
intromessage through that Pool, with the person’s name in the words. Nothing in the envelope says “please introduce me to this entry”, so the curator reads it as a person would. - Intent tags are free text (§3.2). Our decks gather Pools by their
intent_tags, which have no shared vocabulary. We publish the tags each deck gathers, above. A Pool none of them names still opens, under its own name, so a free-text tag never keeps a Pool out of Face Up. - A site’s word about its own Pool has no field (§3.2, §10). Foul Weather Friends asks apps not to read the Check-In Circle. A manifest can say unlisted, not “please don’t read this”; the request lives in its governance rules, and we honor it the way a browser leaves a site alone that asks not to be read.
- “For context only” has no field (§3.2). The Orchard Street Household is listed “so the people we date can see how we are connected”. Nothing in the manifest says a Pool takes no introductions; its charter says it, and we read the charter.
- “Members add it themselves” has no field (§3.2). Drip Line’s Thursday Pool is unlisted and asks members to add its address to an app themselves. The manifest can say unlisted, not that; it lives in the governance rules, and we read them.
- A curator route has no field (§5.4). Whether a yes should go to the curator is in each Pool’s charter and governance rules, in words.
vouching-requiredsometimes means a check at the door and sometimes an introducer for every meeting. We read the words, Pool by Pool, and say what we decided in the About page’s table. - A personal block can’t travel yet (§7.3). Block lists are for known abuse, published by the project or a third party, and every client subscribes. A person’s own block has no message and no list of its own. We keep yours on the other person’s Kindling identity, and point out that your email can hold it too.
- Pages say more than the parsed profile (§2.4). Age, gender, pictures, prompts and answers aren’t fields in
parsed_profile.schema.json. We read them from each person’s own page at display time and never copy them, which is also what §2.5 asks for pictures. Outside dating we show only a page’s job, languages and interests, and only when the Pool lists that very page. - Verification level names (§4.5). The spec text says
email-verified,oauth-verified,curator-vouchedandunverified. The schemas useemail,oauth,curator-vouched,unverifiedandcryptographic. Our JSON follows the schema; the screen shows words. - A vouch is per Pool, and the entry says so (§4.4). Entries carry
curator-vouchedfor our face check and for Drip Line’s belay check, but a parsed profile’s ownverificationrepeats it without naming the Pool. An app that copied that field into another deck would overstate it. We show the level only beside the Pool it belongs to. - The handshake window (§5.2). The spec says 14 days, “configurable per Pool”. The manifest has no field for it, so our requests carry it in
expires_atand our governance rules say so. - Where a field came from (§2.3). The spec asks parsers to record h-card versus inferred fields in
extraction_source. The parsed-profile schema has no such field and forbids extra ones. - The Pool reference and the version field (§6.2, §12.1). The spec says
pool_refandkindling_version; the schemas sayvia_poolandschema_version. We use the schema’s names. - Messaging rule names (§7.2). The spec names
open-to-all,pool-mates-only,vouched-only,no-cold-messagesandminimum-sender-verification. The profile schema hasaccept_fromand ano_cold_messagesflag, and only a Pool can set a sender minimum.