docs(site): the engagement system — cutover 7 of 7 (edge → main)
#28
@@ -32,6 +32,7 @@ Where the console offers free text about security practices, two things are wort
|
|||||||
| App info and performance | Other app data | No | No | Not collected by us. Stored on the device only. |
|
| App info and performance | Other app data | No | No | Not collected by us. Stored on the device only. |
|
||||||
| Messages | Other in-app messages | No | No | Not collected by us. Declare the relay hop in the console’s free-text security section if it asks. |
|
| Messages | Other in-app messages | No | No | Not collected by us. Declare the relay hop in the console’s free-text security section if it asks. |
|
||||||
| Messages | Other user-generated content | No | No | Not collected by us. |
|
| Messages | Other user-generated content | No | No | Not collected by us. |
|
||||||
|
| Messages | Other in-app messages | No | No | Not collected by us. Stored on the device only. |
|
||||||
| Device or other IDs | Device or other IDs | No | No | Not collected. |
|
| Device or other IDs | Device or other IDs | No | No | Not collected. |
|
||||||
|
|
||||||
## Each answer, and why it is the truthful one
|
## Each answer, and why it is the truthful one
|
||||||
@@ -83,12 +84,23 @@ Push is off until you enable it. When you do, the app mints a random, unguessabl
|
|||||||
|
|
||||||
**Messages → Other user-generated content.** Not collected by us.
|
**Messages → Other user-generated content.** Not collected by us.
|
||||||
|
|
||||||
Forum posts, Team activity, character and shard information, notification preferences: all of it is a live read or write against the deployment. Nothing is cached for offline use and nothing is duplicated anywhere else — the app with no signal is an app with no content, which is a limitation and also an accurate description of where the data lives.
|
Forum posts, Team activity, character and shard information, notification preferences: all of it is a live read or write against the deployment. Apart from the notification snapshot described in the next entry, nothing is cached for offline use and nothing is duplicated anywhere else — the app with no signal is an app with almost no content, which is a limitation and also an accurate description of where the data lives.
|
||||||
|
|
||||||
- **Why that answer:** Content is written to the community’s own installation. We have no copy, no access and no way to obtain one.
|
- **Why that answer:** Content is written to the community’s own installation. We have no copy, no access and no way to obtain one.
|
||||||
- **Retention:** Held by the deployment, under its operator’s policy
|
- **Retention:** Held by the deployment, under its operator’s policy
|
||||||
- **Read from:** `PLAN.md §9 section 2`
|
- **Read from:** `PLAN.md §9 section 2`
|
||||||
|
|
||||||
|
### A snapshot of your notifications, so the inbox opens without a signal
|
||||||
|
|
||||||
|
**Messages → Other in-app messages.** Not collected by us. Stored on the device only.
|
||||||
|
|
||||||
|
The app keeps the most recent notifications it has already fetched — at most thirty, and only the first page — on the device, so opening the inbox shows you what you had rather than a spinner. It is a copy of what the deployment already sent you and it is refreshed from there; nothing is written here that was not read from your own account. It is scoped to the account that fetched it, so a second person signing in on the same phone is never shown the first one’s messages.
|
||||||
|
|
||||||
|
- **Why that answer:** The snapshot is written on the phone from data the deployment had already delivered. It is not uploaded anywhere, and no server we operate is on either end of it.
|
||||||
|
- **Retention:** Until you sign out, or the thirty are pushed out by newer ones
|
||||||
|
- **In detail:** Signing out deletes the snapshot outright. It lives in the app’s ordinary preference store rather than the encrypted one — sign-in tokens are the thing that store is for — which is worth stating plainly: on a device where someone has root, these are readable, and they are notification bodies rather than credentials.
|
||||||
|
- **Read from:** `core/inbox/DataStoreInboxCache.kt, data/repository/AuthRepository.kt`
|
||||||
|
|
||||||
### No analytics, no crash reporting, no advertising
|
### No analytics, no crash reporting, no advertising
|
||||||
|
|
||||||
**Device or other IDs → Device or other IDs.** Not collected.
|
**Device or other IDs → Device or other IDs.** Not collected.
|
||||||
@@ -116,4 +128,4 @@ Not part of the Data Safety form — that form is about the app — but a review
|
|||||||
- **Your browser’s user-agent string, truncated** — With the row; blanked on removal.
|
- **Your browser’s user-agent string, truncated** — With the row; blanked on removal.
|
||||||
- **The web server’s access log** — Short-term operational retention, then rotated away.
|
- **The web server’s access log** — Short-term operational retention, then rotated away.
|
||||||
|
|
||||||
Last generated from data dated 2026-08-24. Regenerate with `npm run play:datasafety` after any change to what the app stores.
|
Last generated from data dated 2026-09-01. Regenerate with `npm run play:datasafety` after any change to what the app stores.
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const docsSidebar = [
|
|||||||
{ label: 'Teams', slug: 'docs/administration/teams' },
|
{ label: 'Teams', slug: 'docs/administration/teams' },
|
||||||
{ label: 'Moderation', slug: 'docs/administration/moderation' },
|
{ label: 'Moderation', slug: 'docs/administration/moderation' },
|
||||||
{ label: 'Notifications and email', slug: 'docs/administration/notifications-and-email' },
|
{ label: 'Notifications and email', slug: 'docs/administration/notifications-and-email' },
|
||||||
|
{ label: 'Engagement rules', slug: 'docs/administration/engagement-rules' },
|
||||||
{ label: 'Message templates', slug: 'docs/administration/message-templates' },
|
{ label: 'Message templates', slug: 'docs/administration/message-templates' },
|
||||||
{ label: 'Managing modules', slug: 'docs/administration/managing-modules' },
|
{ label: 'Managing modules', slug: 'docs/administration/managing-modules' },
|
||||||
{ label: 'The shard connection', slug: 'docs/administration/the-shard-connection' },
|
{ label: 'The shard connection', slug: 'docs/administration/the-shard-connection' },
|
||||||
@@ -114,6 +115,7 @@ export const plannedSidebar = {
|
|||||||
'Teams',
|
'Teams',
|
||||||
'Moderation',
|
'Moderation',
|
||||||
'Notifications and email',
|
'Notifications and email',
|
||||||
|
'Engagement rules',
|
||||||
'Message templates',
|
'Message templates',
|
||||||
'Managing modules',
|
'Managing modules',
|
||||||
'The shard connection',
|
'The shard connection',
|
||||||
|
|||||||
175
src/content/docs/docs/administration/engagement-rules.mdx
Normal file
175
src/content/docs/docs/administration/engagement-rules.mdx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
title: Engagement rules
|
||||||
|
description: Decide what your site mails and shows people — the rule editor, saved audiences, the trigger catalog and the send log that answers "did they actually get it".
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
[Notifications and email](/docs/administration/notifications-and-email/) is about where a
|
||||||
|
message goes. [Message templates](/docs/administration/message-templates/) is about what it
|
||||||
|
says. This page is the part in between: **what makes one get sent at all.**
|
||||||
|
|
||||||
|
A **rule** is four decisions — *when* (a trigger), *to whom* (an audience), *by what*
|
||||||
|
(channels), and *how often* (timing). **Admin → Engagement → Rules.**
|
||||||
|
|
||||||
|
## Nothing sends until you turn it on
|
||||||
|
|
||||||
|
Every rule arrives switched **off**. That is true of the ones you make, and it is true of
|
||||||
|
the ones your modules ship with them: install a game module and you get a shelf of ready
|
||||||
|
rules, all dark, none of them mailing anybody. Turning one on is a deliberate, separate
|
||||||
|
act.
|
||||||
|
|
||||||
|
The same caution runs through the rest of the screen. Every rule carries a **hard ceiling
|
||||||
|
on sends per hour** — you cannot save one without a number — because the failure mode of an
|
||||||
|
automated mailer is not a wrong message, it is ten thousand of them at four in the morning.
|
||||||
|
|
||||||
|
<Aside type="caution" title="Upgrading? Your Team emails are here now">
|
||||||
|
Team notification email used to be its own pipeline. It is engagement rules now, and — like
|
||||||
|
every other seeded rule — those four rules arrive **disabled**. If your members were getting
|
||||||
|
Team mail before an upgrade, it stops until you turn them on. The admin dashboard says so
|
||||||
|
while it is true.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## What a rule is made of
|
||||||
|
|
||||||
|
**The trigger** is the event that fires it: a house falling into disrepair, a post being
|
||||||
|
published, a login failing. Pick it from what is registered — see
|
||||||
|
[the catalog](#the-trigger-catalog) below. A rule's trigger is **fixed once the rule
|
||||||
|
exists**: its cooldowns, its pending messages and its whole send history are about one
|
||||||
|
event, so changing it would silently be a different rule wearing the same name. Make a new
|
||||||
|
one instead.
|
||||||
|
|
||||||
|
**The audience** is who hears about it. Some are built in — the person the event is about,
|
||||||
|
everyone subscribed to it, staff. Others come from your modules and are named in their own
|
||||||
|
vocabulary. You can also point a rule at a **saved audience** you composed yourself; see
|
||||||
|
[Audiences](#audiences).
|
||||||
|
|
||||||
|
**The channels** are how it reaches them: on the site, by email, by push. A rule can name
|
||||||
|
more than one, and each channel picks its own template — the same event can be a sentence
|
||||||
|
in the inbox and a properly laid-out letter in the mail.
|
||||||
|
|
||||||
|
**The timing** is the part worth reading twice.
|
||||||
|
|
||||||
|
- A **delay** holds the message before it goes, so a situation that resolves itself never
|
||||||
|
produces a message at all.
|
||||||
|
- **Cancel on** names the events that call it back. A warning that a house is about to
|
||||||
|
collapse waits fifteen minutes and is cancelled outright if the owner turns up and
|
||||||
|
repairs it — nobody is told their house was in danger after it stopped being in danger.
|
||||||
|
- A **cooldown** is the "not again for a while" limit, counted **per person, per subject
|
||||||
|
and per channel**. Per subject, so a cooldown about one house says nothing about another.
|
||||||
|
Per channel, so "one a day about this house" means one email *and* one inbox item, which
|
||||||
|
is what an operator setting that limit means.
|
||||||
|
|
||||||
|
## Audiences
|
||||||
|
|
||||||
|
**Admin → Engagement → Audiences** is where you build a named set of people out of the ones
|
||||||
|
your modules declare — *members of this Team*, *the sitting governors* — and combine them:
|
||||||
|
all of these, any of these, none of these.
|
||||||
|
|
||||||
|
One rule governs the whole screen: **composition narrows and never widens.**
|
||||||
|
|
||||||
|
- The ceiling of a saved audience is **derived** from the tightest thing in it, never
|
||||||
|
chosen. That is true of "any of" too, where the intuitive answer — the widest of the two —
|
||||||
|
is the wrong one. A ceiling says what an expression is *allowed* to reach, not what it
|
||||||
|
happens to resolve to today.
|
||||||
|
- **"None of" is only offered inside an "all of" group.** Alone it would have to mean
|
||||||
|
"everybody except these", which is a broadcast built out of a short list, and it is not
|
||||||
|
offered anywhere it would mean that.
|
||||||
|
- Two audiences with no relationship between them — staff and "the person this is about",
|
||||||
|
say — have no honest combined ceiling, so the save is refused rather than guessing which
|
||||||
|
side to take.
|
||||||
|
|
||||||
|
Before you save a rule, the editor shows you a **reach preview**: a number, never a list of
|
||||||
|
names. It will also tell you when a number is a floor rather than an answer, and when an
|
||||||
|
audience resolves to nobody at all and why.
|
||||||
|
|
||||||
|
## The ceiling, and why a rule will not offer the audience you expected
|
||||||
|
|
||||||
|
Every trigger declares the **widest audience a rule may ever give it**. It is the security
|
||||||
|
boundary of the whole system, and it is set in code by whoever declared the event, not in
|
||||||
|
the admin panel. Staff-only events cannot be widened into public ones by anybody, including
|
||||||
|
you.
|
||||||
|
|
||||||
|
Seven values, and they are a **tree, not a ladder**:
|
||||||
|
|
||||||
|
| Ceiling | Who that is |
|
||||||
|
| --- | --- |
|
||||||
|
| `everyone` | Everyone, including signed-out visitors |
|
||||||
|
| `authenticated` | Any signed-in user |
|
||||||
|
| `subscribers` | Signed-in users subscribed to this event |
|
||||||
|
| `members` | Members of a module-declared list |
|
||||||
|
| `staff` | Staff only — admins, editors and moderators |
|
||||||
|
| `admin` | Administrators only |
|
||||||
|
| `owner` | Only the user the event is about |
|
||||||
|
|
||||||
|
<Aside type="note" title="Fewer people is not less exposure">
|
||||||
|
The tempting reading is a ladder — that a staff-only event could obviously also go to just
|
||||||
|
one person. It cannot, and the example is the whole argument: cheat detection is a
|
||||||
|
staff-only event, and "just one person" would be *the player it was detected on*. The
|
||||||
|
question a ceiling answers is never how many, it is **which**.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
So `staff` does not permit `owner`, `members` does not permit `subscribers`, and the editor
|
||||||
|
simply does not offer you the audiences the trigger forbids. The one exception proves the
|
||||||
|
rule: `admin` sits under `staff`, because every administrator really is staff.
|
||||||
|
|
||||||
|
## The trigger catalog
|
||||||
|
|
||||||
|
**Admin → Engagement → Triggers** lists every event a rule can be built on, and it is
|
||||||
|
read-only on purpose — **there is no table behind it**. A trigger is declared in code, by
|
||||||
|
the site or by an installed module, so what you are looking at is whatever registered on
|
||||||
|
this boot. Uninstall a module and its triggers stop appearing; nothing was deleted.
|
||||||
|
|
||||||
|
Two things it shows that are invisible everywhere else:
|
||||||
|
|
||||||
|
- **The variables** each event carries, with an example of each. This is the list a template
|
||||||
|
is allowed to reference — when a message comes out with a hole in it, this is the screen
|
||||||
|
that says why.
|
||||||
|
- **The ceiling**, so when the rule editor offers you a narrower set of audiences than you
|
||||||
|
expected, you can see the number it is obeying.
|
||||||
|
|
||||||
|
### Dormant rules
|
||||||
|
|
||||||
|
A rule can be switched on and still be unable to fire — most often because the module that
|
||||||
|
declared its trigger, or the audience it points at, is no longer installed. Those are
|
||||||
|
badged **dormant** in the list, with the reason, because "this rule cannot fire" is a
|
||||||
|
different fact from "this rule is off" and you need both. The on/off switch keeps working
|
||||||
|
on a dormant rule, deliberately: a rule whose module has gone is exactly the rule you most
|
||||||
|
want to be able to stop.
|
||||||
|
|
||||||
|
## The send log
|
||||||
|
|
||||||
|
**Admin → Engagement → Send Log** answers one question: *did that person get that message,
|
||||||
|
and if not, why not?* Every attempt is a row — when, what fired it, which user, which
|
||||||
|
channel, and the result. Filter by result to go straight to what failed.
|
||||||
|
|
||||||
|
| Result | What it means |
|
||||||
|
| --- | --- |
|
||||||
|
| **Sent** | Handed to the channel successfully |
|
||||||
|
| **Failed** | The attempt errored — the reason is on the row, not hidden in a tooltip |
|
||||||
|
| **Not sent** | Suppressed before it was attempted: unsubscribed, unverified, or on the [suppression list](/docs/administration/troubleshooting/) |
|
||||||
|
| **Bounced** | The receiving server rejected it after the fact |
|
||||||
|
| **Marked as spam** | The recipient reported it |
|
||||||
|
|
||||||
|
Test sends from the template editor land here too, labelled as such, so you can confirm
|
||||||
|
your own test arrived before turning a rule on for real.
|
||||||
|
|
||||||
|
<Aside type="tip" title="Two things it will not show you, on purpose">
|
||||||
|
**The email address.** The log stores a one-way hash of it — enough to tie a bounce back to
|
||||||
|
a delivery, not enough to become a second address book.
|
||||||
|
|
||||||
|
**A name.** It holds the user id, and that is deliberate: joining the account list in would
|
||||||
|
quietly turn a delivery log into a staff-readable directory. Paste the id into Moderation,
|
||||||
|
which is where a person's record belongs.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## What a game module brings
|
||||||
|
|
||||||
|
A module declares its own triggers and its own audiences, in its own vocabulary, and it may
|
||||||
|
ship rules and message bodies to go with them. The Ultima Online module ships a large family
|
||||||
|
of them — houses falling to ruin, vendors running out of gold, a governor being seated, a
|
||||||
|
guild's fortunes — written in the voice of an in-world office rather than a system alert.
|
||||||
|
|
||||||
|
All of them arrive **disabled**, like every other seeded rule. Read the list in
|
||||||
|
**Admin → Engagement → Triggers**, turn on the ones your shard should send, and check the
|
||||||
|
send log the first time each one fires.
|
||||||
@@ -129,4 +129,5 @@ rejected sender address, a bad password, a relay that will not accept your domai
|
|||||||
|
|
||||||
The log does not store anybody's email address. It keeps a one-way fingerprint instead, so
|
The log does not store anybody's email address. It keeps a one-way fingerprint instead, so
|
||||||
that a bounce can be matched back to a delivery without the log itself becoming a second
|
that a bounce can be matched back to a delivery without the log itself becoming a second
|
||||||
copy of your members' addresses.
|
copy of your members' addresses. The rest of that screen — and the rules that decide a
|
||||||
|
message is sent at all — is [Engagement rules](/docs/administration/engagement-rules/).
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ Four separate delivery paths, each optional, each off until you configure it. A
|
|||||||
configures none of them still works — it just never reaches anyone who is not looking at
|
configures none of them still works — it just never reaches anyone who is not looking at
|
||||||
it.
|
it.
|
||||||
|
|
||||||
|
This page is about the paths themselves. What decides that a particular message gets sent
|
||||||
|
down one of them is a rule — see [Engagement rules](/docs/administration/engagement-rules/).
|
||||||
|
|
||||||
## Email
|
## Email
|
||||||
|
|
||||||
**Admin → Settings → Email delivery.** The site sends contact-form messages, invitations,
|
**Admin → Settings → Email delivery.** The site sends contact-form messages, invitations,
|
||||||
@@ -154,6 +157,10 @@ member sets their own. The defaults are not symmetrical, and the asymmetry is de
|
|||||||
- **Muting a Team silences all three for that Team**, whatever the grid says, without
|
- **Muting a Team silences all three for that Team**, whatever the grid says, without
|
||||||
touching any of their other Teams.
|
touching any of their other Teams.
|
||||||
|
|
||||||
|
The operator's side of the same question — which events exist, and how wide an audience each
|
||||||
|
one may ever be given — is [Engagement rules](/docs/administration/engagement-rules/). When
|
||||||
|
a message went nowhere and you want to know why, the send log there is the screen that says.
|
||||||
|
|
||||||
<Aside type="caution" title="Nothing here retries">
|
<Aside type="caution" title="Nothing here retries">
|
||||||
The announcement dispatcher sends once, and the Team notification bridge states plainly that
|
The announcement dispatcher sends once, and the Team notification bridge states plainly that
|
||||||
a message is sent once and not retried. If Discord is down when a post is published, that
|
a message is sent once and not retried. If Discord is down when a post is published, that
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ it says. What changed is that it is now precise: it stops the emails it came wit
|
|||||||
that Team's push notifications alone, where before it silenced both.
|
that Team's push notifications alone, where before it silenced both.
|
||||||
|
|
||||||
You can change what any of these messages say — see
|
You can change what any of these messages say — see
|
||||||
[Message templates](/docs/administration/message-templates/) — and see who was actually
|
[Message templates](/docs/administration/message-templates/) — decide which of them are sent
|
||||||
sent what in **Admin → Engagement → Send log**.
|
at all under [Engagement rules](/docs/administration/engagement-rules/), and see who was
|
||||||
|
actually sent what in **Admin → Engagement → Send log**.
|
||||||
|
|
||||||
## The Discord bridges
|
## The Discord bridges
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,22 @@ a Team. See [Teams](/docs/administration/teams/).
|
|||||||
- **A missed announcement does not come back.** Nothing retries; the post itself is still
|
- **A missed announcement does not come back.** Nothing retries; the post itself is still
|
||||||
on the site.
|
on the site.
|
||||||
|
|
||||||
|
## Nothing is sent for one particular event
|
||||||
|
|
||||||
|
Mail works, other notifications arrive, but this one thing never produces anything. The
|
||||||
|
answer is almost always in **Engagement → Rules**, and it is one of four:
|
||||||
|
|
||||||
|
- **The rule is off.** Every rule ships disabled, including the ones your modules bring
|
||||||
|
with them, so "installed" is not "on".
|
||||||
|
- **The rule is badged *dormant*.** It is switched on but cannot fire — usually because the
|
||||||
|
module that declared its trigger, or the audience it points at, is no longer installed.
|
||||||
|
- **It fired and was held back by its own cooldown**, which is per person, per subject and
|
||||||
|
per channel. The Send Log shows nothing for a message that was never queued.
|
||||||
|
- **The audience resolves to nobody.** The rule editor's reach preview is the fastest way
|
||||||
|
to find that out — it will tell you the count is zero and why.
|
||||||
|
|
||||||
|
[Engagement rules](/docs/administration/engagement-rules/) walks through all four.
|
||||||
|
|
||||||
## One person stopped receiving email
|
## One person stopped receiving email
|
||||||
|
|
||||||
Everyone else is getting mail, so the transport is fine. Check
|
Everyone else is getting mail, so the transport is fine. Check
|
||||||
|
|||||||
@@ -285,9 +285,10 @@ export const collected = [
|
|||||||
title: 'Everything you read and post in the app',
|
title: 'Everything you read and post in the app',
|
||||||
body:
|
body:
|
||||||
'Forum posts, Team activity, character and shard information, notification ' +
|
'Forum posts, Team activity, character and shard information, notification ' +
|
||||||
'preferences: all of it is a live read or write against the deployment. Nothing is ' +
|
'preferences: all of it is a live read or write against the deployment. Apart from ' +
|
||||||
'cached for offline use and nothing is duplicated anywhere else — the app with no ' +
|
'the notification snapshot described in the next entry, nothing is cached for ' +
|
||||||
'signal is an app with no content, which is a limitation and also an accurate ' +
|
'offline use and nothing is duplicated anywhere else — the app with no signal is ' +
|
||||||
|
'an app with almost no content, which is a limitation and also an accurate ' +
|
||||||
'description of where the data lives.',
|
'description of where the data lives.',
|
||||||
retention: {
|
retention: {
|
||||||
summary: 'Held by the deployment, under its operator’s policy',
|
summary: 'Held by the deployment, under its operator’s policy',
|
||||||
@@ -304,6 +305,40 @@ export const collected = [
|
|||||||
'access and no way to obtain one.',
|
'access and no way to obtain one.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'app-inbox-cache',
|
||||||
|
scope: 'app',
|
||||||
|
title: 'A snapshot of your notifications, so the inbox opens without a signal',
|
||||||
|
body:
|
||||||
|
'The app keeps the most recent notifications it has already fetched — at most ' +
|
||||||
|
'thirty, and only the first page — on the device, so opening the inbox shows you ' +
|
||||||
|
'what you had rather than a spinner. It is a copy of what the deployment already ' +
|
||||||
|
'sent you and it is refreshed from there; nothing is written here that was not ' +
|
||||||
|
'read from your own account. It is scoped to the account that fetched it, so a ' +
|
||||||
|
'second person signing in on the same phone is never shown the first one’s ' +
|
||||||
|
'messages.',
|
||||||
|
retention: {
|
||||||
|
summary: 'Until you sign out, or the thirty are pushed out by newer ones',
|
||||||
|
detail:
|
||||||
|
'Signing out deletes the snapshot outright. It lives in the app’s ordinary ' +
|
||||||
|
'preference store rather than the encrypted one — sign-in tokens are the thing ' +
|
||||||
|
'that store is for — which is worth stating plainly: on a device where someone ' +
|
||||||
|
'has root, these are readable, and they are notification bodies rather than ' +
|
||||||
|
'credentials.',
|
||||||
|
},
|
||||||
|
source: 'core/inbox/DataStoreInboxCache.kt, data/repository/AuthRepository.kt',
|
||||||
|
play: {
|
||||||
|
category: 'Messages',
|
||||||
|
type: 'Other in-app messages',
|
||||||
|
collected: false,
|
||||||
|
shared: false,
|
||||||
|
answer: 'Not collected by us. Stored on the device only.',
|
||||||
|
because:
|
||||||
|
'The snapshot is written on the phone from data the deployment had already ' +
|
||||||
|
'delivered. It is not uploaded anywhere, and no server we operate is on either ' +
|
||||||
|
'end of it.',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'app-no-analytics',
|
id: 'app-no-analytics',
|
||||||
scope: 'app',
|
scope: 'app',
|
||||||
@@ -401,6 +436,34 @@ export const collected = [
|
|||||||
},
|
},
|
||||||
source: 'website server/db/schema.sql — team_forum_*, mod_actions, content_reports',
|
source: 'website server/db/schema.sql — team_forum_*, mod_actions, content_reports',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'deploy-engagement',
|
||||||
|
scope: 'deployment',
|
||||||
|
title: 'Notifications, and the record of what was sent',
|
||||||
|
body:
|
||||||
|
'An operator can have the site notify people about things that happen on it — on ' +
|
||||||
|
'the site, by email, by push — so an address is now used for more than getting ' +
|
||||||
|
'into an account. Each member chooses this per notification and per channel, and ' +
|
||||||
|
'email and push are both off until they ask for them. Alongside that the site ' +
|
||||||
|
'keeps a delivery log: what fired, which account, which channel, whether it ' +
|
||||||
|
'arrived, and a one-way hash of the address rather than the address. Addresses ' +
|
||||||
|
'that bounce or are reported as spam go on a suppression list, which stores the ' +
|
||||||
|
'same hash plus a masked form (`d***@example.com`, never the local part) so an ' +
|
||||||
|
'operator can see what was suppressed without the list becoming a second address ' +
|
||||||
|
'book.',
|
||||||
|
retention: {
|
||||||
|
summary: 'Kept until the operator removes them; nothing here expires on its own',
|
||||||
|
detail:
|
||||||
|
'Stated plainly because it is the answer people assume the other way round: ' +
|
||||||
|
'the delivery log, the suppression list and the per-person rate limits have no ' +
|
||||||
|
'retention sweep, so they are as long as the site is old. Deleting an account ' +
|
||||||
|
'detaches its rows from it rather than deleting them — a delivery history stops ' +
|
||||||
|
'naming a person, and a suppressed address stays suppressed.',
|
||||||
|
},
|
||||||
|
source:
|
||||||
|
'website server/db/schema.sql — engagement_sends, engagement_suppressions, ' +
|
||||||
|
'notification_channel_prefs',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'deploy-game-data',
|
id: 'deploy-game-data',
|
||||||
scope: 'deployment',
|
scope: 'deployment',
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const legal = {
|
|||||||
* from git: a build timestamp would move on every rebuild and tell a reader nothing,
|
* from git: a build timestamp would move on every rebuild and tell a reader nothing,
|
||||||
* and a commit date would move when a stylesheet changed.
|
* and a commit date would move when a stylesheet changed.
|
||||||
*/
|
*/
|
||||||
lastUpdated: '2026-08-24',
|
lastUpdated: '2026-09-01',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum age to sign up for the beta. The org lead's decision, 2026-08-24 (D31).
|
* The minimum age to sign up for the beta. The org lead's decision, 2026-08-24 (D31).
|
||||||
|
|||||||
Reference in New Issue
Block a user