Phase 6's foundation: the fan-out shape the existing pipeline could not express. `pushDispatch.publish` answers "everyone subscribed to a stream" and "this one owner". Team notifications need "these N users", because Team scoping cannot live in a stream id: the catalog is a static registration validated at boot against a namespaced pattern, so a stream per Team is unexpressible, and stream ids are stored in `notification_subscriptions` rows that would need collecting every time a Team archived. So there are FOUR fixed core streams and the Team lives entirely in the recipient set. `team_notification_prefs` is opt-out for push and opt-IN for email — the two sinks default opposite ways, and the asymmetry lives in the column defaults so no condition anywhere has to remember it. One recipient query serves all four streams, because §6.2's two populations are the same set written twice: "active members with a user_id plus active grants" IS "everyone with resolved forum access". Mutes are subtracted in SQL rather than by the caller — there is no function here that returns an unfiltered set. Co-Authored-By: Claude <noreply@anthropic.com>
81 lines
3.6 KiB
JavaScript
81 lines
3.6 KiB
JavaScript
// ── Core's own push-notification streams ───────────────────────────────────
|
|
//
|
|
// What is left of config/notificationStreams.js once the shard-derived catalog
|
|
// moved to config/shardStreams.js (MODULE_SYSTEM.md §1.8: push INFRASTRUCTURE is
|
|
// core, the CATALOG is content). `news.post` is produced by the website's own
|
|
// posts path, not by any game feed, and the four `team.*` streams by core's own
|
|
// Team sync and forum.
|
|
//
|
|
// Registered through modules/registries.js like any module's, and read back
|
|
// through it — nothing imports this file to get "the catalog", because the
|
|
// catalog is core's plus every module's.
|
|
//
|
|
// Phase 6 added the four Team streams below. They are core's for the same reason
|
|
// the Team tables are: a module supplies who is in a Team, but who may be told
|
|
// about it is the access resolver's answer, and that is core's (TEAMS.md Part 6).
|
|
//
|
|
// The payload that ever leaves the server is a CONTENT-FREE tickle
|
|
// ({ stream, ref }); the app wakes and PULLS the real, ownership-checked content
|
|
// over the authenticated API (docs/android/PLAN.md §11).
|
|
|
|
const STREAMS = [
|
|
{
|
|
id: 'news.post',
|
|
label: 'News posts',
|
|
description: 'New news / Five-on-Friday / newsletter posts.',
|
|
personal: false,
|
|
requiresLinkedAccount: false,
|
|
},
|
|
// ── Teams (TEAMS.md §6.2, phase 6) ───────────────────────────────────────
|
|
//
|
|
// FOUR streams, and not one per Team. The catalog is a static registration
|
|
// validated at boot; it has no way to express an unbounded runtime-created set,
|
|
// and a stream id per Team would leave rows in notification_subscriptions to
|
|
// collect every time a Team archived. Which Team an event came from lives in
|
|
// the RECIPIENT SET (utils/teamNotify.js) and in the `ref`, never in the id.
|
|
//
|
|
// `requiresLinkedAccount: false` on all four is deliberate and reads oddly.
|
|
// These are game-sourced events, so the instinct is to demand a linked game
|
|
// account — but a forum-granted user with no game identity at all is exactly
|
|
// the population §2.5 path 3 exists for, and they are a legitimate recipient of
|
|
// `team.forum.post`. The flag would refuse them a toggle they have every right
|
|
// to. What enforces who gets what is the recipient computation, which asks the
|
|
// access resolver; the stream flag is not a second, weaker copy of that rule.
|
|
//
|
|
// `personal: false` for the same reason it is false on news.post: these are not
|
|
// owner-keyed events about one account's own property. `publishToUsers` is a
|
|
// third fan-out shape alongside "everyone subscribed" and "this one owner", and
|
|
// the catalog has no flag for it because the flag would say nothing a caller
|
|
// does not already know by choosing the function.
|
|
{
|
|
id: 'team.member.joined',
|
|
label: 'Team — new member',
|
|
description: 'Someone joined a Team you belong to.',
|
|
personal: false,
|
|
requiresLinkedAccount: false,
|
|
},
|
|
{
|
|
id: 'team.leadership.changed',
|
|
label: 'Team — leadership change',
|
|
description: 'Leadership changed in a Team you belong to.',
|
|
personal: false,
|
|
requiresLinkedAccount: false,
|
|
},
|
|
{
|
|
id: 'team.forum.post',
|
|
label: 'Team — new forum post',
|
|
description: 'A new thread or reply in a Team forum you can read.',
|
|
personal: false,
|
|
requiresLinkedAccount: false,
|
|
},
|
|
{
|
|
id: 'team.announcement',
|
|
label: 'Team — announcements',
|
|
description: 'A leader posted an announcement in a Team you can read.',
|
|
personal: false,
|
|
requiresLinkedAccount: false,
|
|
},
|
|
]
|
|
|
|
module.exports = { STREAMS }
|