Alpha safety measurement: the survey portal โ
Status: DRAFT. Direction set by the operator 2026-08-16. Risks below are open. Issue: #867Governs: the privacy-architecture skill (re-identification is the harm).
The design โ
A random portal, separate from the app, where people fill in a survey.
Two ways in:
- A QR code at a lantern event
- A pop up later, because we know they lit a lantern at the event
Merchants offer coupon incentives for taking the survey.
The submission is NEVER tied to their account.
What we collect, all optional: demographic, identity, orientation, age range, safety rating, what went right, what went wrong, what we could do better, and an optional message.
What this gets right โ
The free-text fields replace interviews, and beat them. An earlier draft of this document proposed consented debriefs for depth. Written answers to "what went wrong" carry no interviewer, so they do not have to be said to a founder's face. That removes social desirability bias rather than managing it, and it scales past the number of conversations one person can have.
The coupon solves response rate, which is the standing weakness of any anonymous instrument and the reason most of them fail.
A separate portal is unlinked by construction, not by our own discipline. A survey inside the app would depend on us remembering not to attach identifiers.
Open risks โ
1. The coupon: settled, and do not harden it later โ
Operator decision 2026-08-16: the incentive is a code word told to the staff, a clubhouse password. Finish the survey, see the word, say it at the bar, get the coupon.
This breaks the link completely. Nothing in our system observes who submitted, because the redemption never touches our system at all.
Two constraints that keep it working, both easy to undo by accident:
- The word is shared per event, never per submission. A unique code handed out at submit time is a submission identifier, and rebuilds the exact link this design exists to prevent.
- It is meant to be leaky. Someone who did not take the survey can be told the word and get a coupon. That is acceptable and it is the price of the property. Anyone later "fixing" this with one-time codes or redemption tracking would be reintroducing the linkage while believing they are tightening security.
2. The pop up must open the portal, not carry anything to it โ
Prompting in-app is fine. The prompt should open the portal as a plain link, with no token, no uid, and no session passed through. The app's only job is showing the door.
3. Granularity: keep it, and say so plainly โ
Operator decision 2026-08-16: do not coarsen the buckets.
I don't think we can make it that coarse because that information is valuable to make sure we are refining our safety. Best we can do is be up front about it as much as possible.
The residual risk is real and it is not ours to remove. A respondent who is the only person of a given identity at a small event may be recognisable to other attendees from their own free-text answer. Not to us: the submission is already unlinked from any account.
Why up-front beats coarsening, rather than merely being the fallback. Every field is optional, so the person deciding whether their answers make them identifiable is the person who bears the risk. Coarsening takes that decision away from them and hands it to us, and it destroys the signal the survey exists to produce. Telling them clearly and letting them choose is both more useful and more respectful of the choice.
What this makes a hard requirement: the portal has to SAY it, in plain language, before the optional questions rather than buried in a policy link. "Up front as much as possible" is a copy decision, and copy is exactly where it either happens or quietly does not.
One thing consent at collection does NOT cover: what we do with a response afterwards. Publishing a breakdown small enough to identify someone, or showing a raw response to a venue, is our decision and not theirs. That still needs a rule.
4. Where the responses live: its own store, and nothing else โ
Operator decision 2026-08-16:
Perhaps endpoint of the data is literally its own BQ or firestore table, completely and utterly decoupled. Essentially building its own form submission collection. We do not collect ANYTHING, except for what the user submits. Only tether is the event/venue it is related to.
This puts the guarantee back into architecture rather than policy, which is where the axiom wants it. A store that holds nothing but submitted answers plus an event tether cannot leak a link it does not contain.
The hard part is "we do not collect ANYTHING", because a web form collects by default. Left alone, the infrastructure records request IP, user agent, and a precise arrival timestamp in Cloud Run and CDN access logs, before any of our code runs. IP plus a timestamp at a small event is re-identifying, and it lands in logs rather than in the table, so a clean-looking table proves nothing about it.
So the claim is testable and the default violates it. To make it true:
- No request IP retained for the submission endpoint, in application logs, Cloud Run request logs, or the CDN in front of it.
- No precise server-side timestamp. If a submission time is needed at all, store the event rather than the instant. Insert-time metadata in BigQuery is a timestamp whether or not a column says so.
- No auto-generated identifier that encodes ordering or arrival time.
- Verify by trying to re-identify a test submission from everything the system retains. If it can be done, the claim is not yet true.
Open: same project, or a separate one. A separate GCP project makes "never joinable" architectural, because there is no credential that reaches both. A separate collection in the same project makes it a matter of discipline and rules, which is the weaker form and the one that erodes. Worth deciding before it is built rather than after.
The event tether is a tether to a place and a time, not a person. It is what makes per-event analysis possible, and combined with granular demographics at a small event it is also what enables recognition by other attendees. That tradeoff is already ruled on in risk 3 and this does not reopen it.
The stopping rule โ
Operator decision 2026-08-16:
security is too most priority even if it delays launches. it must be rated to level of danger before release. code reds are always fixed immediately.
What this settles: safety outranks the launch date. Not balanced against it, outranks it. Responses are RATED by level of danger before release, and a code red is fixed immediately rather than scheduled.
Level names settled 2026-08-16: red, orange, yellow. What each one triggers is NOT settled. The table below is a proposal, and three questions under it are open.
| Level | What it means | What it triggers |
|---|---|---|
| Red | Someone was harmed, or a report describes something that could harm someone | Fixed immediately. Launch stops until it is closed |
| Orange | A pattern that could become red if it repeats | Fixed before the next event |
| Yellow | Discomfort with no danger in it | Backlog. Does not gate anything |
Three questions still open on the scale:
- "Stops production" means at least three different things. Halt the next event, halt the Alpha launch, or pull the app. A report that justifies cancelling Thursday is not automatically one that justifies taking the product down.
- Most severe reports will have no code fix. Someone behaving badly is not a bug. A scale assuming every red maps to a fix mishandles the most common severe case, so it likely needs a branch: product, event format, venue, or moderation.
- One report versus a pattern. Halting on any single report is unworkable. Waiting for a pattern is too slow to protect the person who reported. There is probably no clean answer here, only a choice about which error to prefer.
Consequence worth naming: rating responses means somebody reads free-text safety reports and decides how dangerous each one is. That is a real duty with a response time attached, not a reporting chore, and it needs an owner before the first event rather than after the first red.
Not in scope โ
Safety FEATURES, tracked separately: #631, #630, #130, #128, #129, #158. This is about measuring, not about making people safer.
Precedent to not repeat โ
#881: a dashboard metric that reads structurally zero because the fields it counts are deleted on every save, and looked fine for months. A measurement built on data we deliberately cannot read fails the same way.