From 9aa6a53518189f2d60f4afc8fb723d3209218825 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Sun, 1 Nov 2026 00:23:40 +0100 Subject: [PATCH 01/16] chore: spec for onboarding process --- GLOSSARY.md | 54 +++++++ ...onboarding-without-guild-members-intent.md | 137 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 GLOSSARY.md create mode 100644 docs/adr/0001-onboarding-without-guild-members-intent.md diff --git a/GLOSSARY.md b/GLOSSARY.md new file mode 100644 index 0000000..40035f4 --- /dev/null +++ b/GLOSSARY.md @@ -0,0 +1,54 @@ +# Web Dev & Design Bot + +A Discord bot for the Web Dev & Design server. This glossary covers the language of the new-member onboarding flow. + +## Onboarding + +**Onboarding**: +The bot's own, custom flow that moves a new member from limited access to full access through a series of roles. +_Avoid_: Discord onboarding (that is a different thing), welcome flow, verification + +**Discord onboarding**: +Discord's built-in question flow shown when someone joins, in which the member picks a role such as Developer or Designer. +_Avoid_: Onboarding (reserved for the bot's flow) + +**Everyone**: +A member holding no onboarding role, for example because they skipped Discord onboarding. They can only write in lounge and can read a few channels, including message mods. +_Avoid_: Roleless member, guest + +**Start role** (`onboarding:start`): +The role a member receives from Discord onboarding. It unlocks write access to the first set of channels and is the entry point of Onboarding. + +**7-day role** (`onboarding:7day`): +Granted once a member has been in the server for 7 days. + +**Messages role** (`onboarding:messages`): +Granted once a member's counted messages reach 400 characters in total. + +**Finished role** (`onboarding:finished`): +Granted when a member holds both the 7-day role and the messages role. It unlocks the rest of the server. The 7-day and messages roles are removed when it is granted. + +**Dormant role** (`onboarding:dormant`): +Granted to a member who has not reached the finished role 3 months after joining. It strips their onboarding progress and signals that they must take action to return. + +**Rectification**: +Leaving dormancy, either by the member pressing the button in the dormant channel or by a moderator using a command. It removes the dormant role and restarts the 7-day clock, the message count and the 3-month deadline from that moment. + +**Restart anchor**: +The moment from which a member's Onboarding clocks run: their join date, or their latest rectification if that is later. + +**Dormant channel**: +The one channel a dormant member can read besides message mods. It explains that their account is dormant and holds the button for rectification. + +**Counted message**: +A message that contributes characters toward the messages role. Messages sent before the start role count; messages deleted or caught by spam detection do not. Only the text counts, stripped of whitespace, markdown formatting and links; attachments add nothing. + +**Exempt member**: +A bot or moderator. They never receive onboarding roles and never go dormant. + +**Legacy member**: +A member who joined before Onboarding launched. They skip the flow and receive the start, finished and legacy roles the first time the bot sees them post. +_Avoid_: Grandfathered member (gendered term) + +**Legacy role** (`onboarding:legacy`): +A permanent marker that tells moderators a member was given the finished role by being a legacy member and did not earn it. diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.md b/docs/adr/0001-onboarding-without-guild-members-intent.md new file mode 100644 index 0000000..2ed710f --- /dev/null +++ b/docs/adr/0001-onboarding-without-guild-members-intent.md @@ -0,0 +1,137 @@ +--- +status: accepted +--- + +# Onboarding: a role-based member journey, built without the GuildMembers intent + +Terms in **bold** are defined in [GLOSSARY.md](../../GLOSSARY.md). + +## 1. What Onboarding is + +New members of the Web Dev & Design server start with very little access and earn the rest of the server over time. **Onboarding** is the bot's own flow for this. It is separate from **Discord onboarding**, the question Discord shows at join, where the member picks a role such as Developer or Designer. + +The purpose is to let newcomers take part in a few friendly channels straight away, while the rest of the server is held back until they have shown they are present and engaged. Members who never come back lose access again. + +### The journey + +| Stage | How a member gets there | What they can do | +| ---------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Everyone** | Joined, but has no onboarding role (for example skipped Discord onboarding) | Write in lounge, read a few channels, read message mods to open a ticket | +| **Start role** | Granted by Discord onboarding | Also write in help forum, ai, gaming, real life, frontend, design, backend and showcase | +| **7-day role** | 7 days after the **restart anchor** | Milestone only | +| **Messages role** | 400 **counted message** characters since the restart anchor | Milestone only | +| **Finished role** | Holds start, 7-day and messages | Full server access. Permanent. The 7-day and messages roles are removed | +| **Dormant role** | 3 months after the restart anchor without the finished role | Loses access to everything except message mods and the **dormant channel**. The 7-day and messages roles are removed; the start role stays | +| **Legacy member** | Joined before launch, first seen posting | Receives start, finished and **legacy role** at once | + +### Rules + +- The restart anchor is the later of the member's join date and their latest rectification. The 7-day clock, the message count and the 3-month dormancy deadline all run from it. +- Dormancy applies to anyone without the finished role, including members who never held the start role. +- Finishing needs the start role. A member could meet the 7-day and message conditions without it, but the finished role waits until Discord onboarding has been completed. +- Counted messages are text only, with whitespace, markdown syntax and links removed. Attachments add nothing. Messages written before the start role count. Messages that are deleted, or removed by spam detection, do not. Edits recount the message. +- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role, set a new restart anchor and discard the old message count. +- A member who leaves and rejoins starts from nothing, even if they had finished before. +- Bots and moderators (**exempt members**) are never touched. +- Which channels each role can see or write in is configured by moderators in Discord. The bot only adds and removes roles. It never edits channel permission overwrites. + +## 2. The decision + +Onboarding is implemented as a role reconciler that works **without the GuildMembers privileged intent**. It relies on a small tracking table, the join system message, per-message events and a daily job that fetches members one by one. + +### Why not use the intent + +The intent is the obvious route. It delivers join, leave and role-update events and lets the bot list every member. It is privileged. Discord's [Gateway documentation](https://docs.discord.com/developers/events/gateway) says apps with fewer than 10,000 users can switch privileged intents on in the Developer Portal, and approval is needed above that. This app already needed approval for the Message Content intent, so the same process would apply to GuildMembers. We chose not to depend on an approval that Discord controls and could refuse or revoke. + +### What this costs + +- Roles granted outside the bot (Discord onboarding giving the start role) are noticed late: when the member next posts, or at the next daily run. +- Leaves are noticed late, at the next daily run. +- Time-based roles (7-day, dormant) are applied once a day, so they can arrive up to a day late. +- Existing members can't be listed, so they become **legacy members** one at a time, as they post. +- A silent member whose join message was missed is invisible to the daily run and will never go dormant. + +## 3. Technical design + +### Signals the bot uses + +| Need | Source | Needs privileged intent | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| A member joined | System join message (`MessageType.UserJoin`) in the server's system channel. `message.author` is the joiner | No (GuildMessages) | +| Join date, roles | `message.member` on every guild message (`joinedAt`, `roles`) | No | +| Message counted | `MessageCreate` | No | +| Message edited | `MessageUpdate` | No | +| Message uncounted | `MessageDelete` and `MessageDeleteBulk` | No | +| Member left | Daily run fetches each tracked member by ID and treats Discord's "Unknown Member" error (code 10007) as a leave | No | +| Start role granted | Seen on the member's next message, or when the daily run fetches them | No | + +Spam detection removes messages with `bulkDelete`. discord.js reports that with a separate bulk-delete event, and the bot has no delete listeners today. Both events must be handled, otherwise spam would still count toward the 400 characters. + +### Data + +Two new Prisma models in [schema.prisma](../../prisma/schema.prisma): + +- **Onboarding**: one row per member who is in progress or dormant. Columns are the user ID (primary key), join date and restart anchor. The row is deleted when the member finishes or leaves. +- **OnboardingMessage**: one row per counted message. Columns are the message ID (primary key), the user ID (a foreign key to `Onboarding` that cascades on delete) and the counted length. A member's total is the sum of their rows. A message's creation time is not stored: messages before the anchor are filtered when they arrive, and the time is encoded in the message ID if it is ever needed. Rectification deletes the member's rows. + +Roles are not mirrored in the database. Discord's role list is the source of truth for the current stage, and the tables hold only what Discord cannot tell us (the anchor and the message lengths). + +### Why message records get their own table + +Counting a message, uncounting a deleted one and recounting an edited one are each a single statement keyed by message ID, so concurrent events for the same member cannot overwrite each other. A delete event for a message that is not in the bot's cache carries only the message ID and no author, and the primary key finds the owner directly. Replays (startup catch-up, duplicate events) become upserts on the primary key and cannot double-count. The cascading foreign key removes a member's messages when their row goes. + +Storing the messages as a JSON column on `Onboarding` was considered and rejected; see section 4. + +### One decision function + +All entry points call the same two pieces: + +1. A pure function takes the member's roles, restart anchor, counted characters, join date and the current time, and returns the roles to add and remove. It has no Discord calls, so it can be unit-tested. +2. An applier makes the role changes and writes a line to the onboarding log channel. + +Evaluation order inside the pure function: + +1. **Exempt member**: nothing. +2. **Finished role present**: remove any leftover 7-day and messages roles, stop tracking. +3. **Joined before the launch date**: add start, finished and legacy. +4. **Dormant role present**: nothing until rectification. +5. **3 months past the anchor**: remove 7-day and messages, add dormant. +6. **7 days past the anchor**: add 7-day. +7. **400 counted characters**: add messages. +8. **Start, 7-day and messages all held**: add finished, remove 7-day and messages. + +Role changes run one at a time, as the existing [addRoleToUser](../../src/util/add-role-to-user.ts) does, so a large first run stays inside Discord's rate limits. Functions throw when they cannot complete their job and the caller handles it. + +### Entry points + +- **Join message**: create the tracking row, or reset it if the member is already tracked with an earlier join date. Handling is idempotent so replays are safe. +- **Any guild message from a member who is not exempt and not finished**: if there is no tracking row, create one from `message.member.joinedAt`. Record the counted length (messages before the restart anchor are ignored), then evaluate. +- **Message edit and delete events**: recompute or remove the message record, then evaluate. +- **Daily job**: scheduled with `node-cron` at a fixed UTC time, like the showcase digest. For each tracked member it fetches the member by ID. If the member is gone, their data is deleted. Otherwise it re-evaluates them. +- **Startup catch-up**: fetch recent history of the system channel and replay join messages the bot missed while offline. +- **Rectification**: a shared function checks the member holds the dormant role (throws if not), removes it, sets the anchor to now, deletes the message records and evaluates. The `/onboarding reactivate` slash command and the dormant-channel button both call it. + +### Dormant channel + +At startup the bot makes sure one fixed message with a "Reactivate" button exists in the dormant channel, and posts it if missing. The placeholder text says the account has been dormant and the member should press the button to restart onboarding. The final wording is a constant that will be replaced later. The button is handled by the persistent interaction handlers so it survives restarts, and it rejects members without the dormant role. + +### Configuration + +- **Constants** in one onboarding config file: counted-character threshold (400), days until the 7-day role (7) and dormancy period (3 months), daily-job time, launch-date constant. +- **Required env vars** in [env.ts](../../src/env.ts): the five onboarding role IDs plus the legacy role ID, the dormant channel ID and the onboarding log channel ID, and the launch date. The optional `ONBOARDING_ROLE_ID` and `ONBOARDING_CHANNEL_ID` and the unused `/onboarding` stub are removed. +- **Permissions**: the bot's own role must sit above all onboarding roles and have Manage Roles, and it needs View Channel on the system channel. +- The GuildMembers intent stays off. No change to the intents in [index.ts](../../src/index.ts). + +## 4. Considered options + +- **Apply for the GuildMembers intent.** Simplest design. It would also give events for role changes and leaves, and a list of all members for a clean backfill. Rejected because it depends on Discord's approval, which has been needed before and is not ours to decide. +- **Keep a running character total per member instead of per-message records.** Less storage. Rejected because deleted and spam-removed messages must stop counting, and a total alone cannot tell which characters to subtract. +- **One table, with the per-message lengths in a JSON column on `Onboarding`** (message ID mapped to counted length). Simpler schema, one read for a member's whole state, and one delete for cleanup. Rejected for three reasons. Every update is a read-modify-write of the whole column, so two events for the same member, such as a new message during a spam bulk-delete, can silently overwrite each other unless updates are serialised per member. A delete event for an uncached message gives only the message ID, so finding the member who owns it means scanning every member's JSON with raw SQL, since Prisma cannot filter inside JSON on SQLite as far as we know. And the total would be computed in code, with the whole blob rewritten on every message. Fixing the first two with a per-member lock and a message-to-member lookup would rebuild the second table in a worse form. +- **Hourly job instead of daily.** Faster role delivery. Not chosen: a daily job was the requirement and the lag is acceptable. +- **Bot manages channel permission overwrites.** Would let the bot enforce what "dormant" means. Rejected because channel permissions belong to moderators and the onboarding flow is meant to be roles only. + +## 5. To verify before building + +- That the bot can see and read the system join messages in the real server, and that welcome messages are enabled there. +- That fetching a single member by ID works with the intent off. Discord's docs say only List Guild Members requires the intent. The Get Guild Member page does not mention one, and the existing stub in [features/onboarding](../../src/features/onboarding/index.ts) already calls it. +- Whether the `messages` role should be revoked if later deletions drop a member below the threshold before they finish. The current design reconciles in that direction, but it needs a decision. From 64ca11df584df1a429f08c55e87dafd55eb019d1 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Sun, 1 Nov 2026 01:11:46 +0100 Subject: [PATCH 02/16] chore: update row deletion and user leaves --- ...1-onboarding-without-guild-members-intent.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.md b/docs/adr/0001-onboarding-without-guild-members-intent.md index 2ed710f..ca4b2da 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.md @@ -30,8 +30,8 @@ The purpose is to let newcomers take part in a few friendly channels straight aw - Dormancy applies to anyone without the finished role, including members who never held the start role. - Finishing needs the start role. A member could meet the 7-day and message conditions without it, but the finished role waits until Discord onboarding has been completed. - Counted messages are text only, with whitespace, markdown syntax and links removed. Attachments add nothing. Messages written before the start role count. Messages that are deleted, or removed by spam detection, do not. Edits recount the message. -- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role, set a new restart anchor and discard the old message count. -- A member who leaves and rejoins starts from nothing, even if they had finished before. +- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role and set a new restart anchor. The message count is already empty, because it is cleared when the member becomes dormant. +- A member who leaves loses their Onboarding progress the next time the daily job runs. If they rejoin before that run, nothing changes: their restart anchor and counted messages stay as they were. If they rejoin after it, they start Onboarding again, even if they had finished before. Counted messages are only deleted in two cases: their onboarding record is deleted (they finished, or the daily job found they had left), or they become dormant. - Bots and moderators (**exempt members**) are never touched. - Which channels each role can see or write in is configured by moderators in Discord. The bot only adds and removes roles. It never edits channel permission overwrites. @@ -50,6 +50,7 @@ The intent is the obvious route. It delivers join, leave and role-update events - Time-based roles (7-day, dormant) are applied once a day, so they can arrive up to a day late. - Existing members can't be listed, so they become **legacy members** one at a time, as they post. - A silent member whose join message was missed is invisible to the daily run and will never go dormant. +- Whether a leave resets progress depends on timing. A member who leaves and rejoins before the daily job runs keeps their anchor and counted messages, while one who rejoins after the job has deleted their row starts from zero. That follows from detecting leaves only in the daily job. ## 3. Technical design @@ -72,13 +73,15 @@ Spam detection removes messages with `bulkDelete`. discord.js reports that with Two new Prisma models in [schema.prisma](../../prisma/schema.prisma): - **Onboarding**: one row per member who is in progress or dormant. Columns are the user ID (primary key), join date and restart anchor. The row is deleted when the member finishes or leaves. -- **OnboardingMessage**: one row per counted message. Columns are the message ID (primary key), the user ID (a foreign key to `Onboarding` that cascades on delete) and the counted length. A member's total is the sum of their rows. A message's creation time is not stored: messages before the anchor are filtered when they arrive, and the time is encoded in the message ID if it is ever needed. Rectification deletes the member's rows. +- **OnboardingMessage**: one row per counted message. Columns are the message ID (primary key), the user ID (a foreign key to `Onboarding` that cascades on delete) and the counted length. A member's total is the sum of their rows. A message's creation time is not stored: messages before the anchor are filtered when they arrive, and the time is encoded in the message ID if it is ever needed. A member's rows are deleted in exactly two cases: their `Onboarding` row is deleted, or they become dormant. Roles are not mirrored in the database. Discord's role list is the source of truth for the current stage, and the tables hold only what Discord cannot tell us (the anchor and the message lengths). ### Why message records get their own table -Counting a message, uncounting a deleted one and recounting an edited one are each a single statement keyed by message ID, so concurrent events for the same member cannot overwrite each other. A delete event for a message that is not in the bot's cache carries only the message ID and no author, and the primary key finds the owner directly. Replays (startup catch-up, duplicate events) become upserts on the primary key and cannot double-count. The cascading foreign key removes a member's messages when their row goes. +Counting a message, uncounting a deleted one and recounting an edited one are each a single statement keyed by message ID, so concurrent events for the same member cannot overwrite each other. A delete event for a message that is not in the bot's cache carries only the message ID and no author, and the primary key finds the owner directly. Replays (startup catch-up, duplicate events) become upserts on the primary key and cannot double-count. Message rows are removed by a cascade, not by application code. `OnboardingMessage` declares its relation to `Onboarding` with `onDelete: Cascade`, so deleting a member's `Onboarding` row deletes all of their `OnboardingMessage` rows in the same operation. The code that finishes a member or handles a leave only deletes the `Onboarding` row and cannot forget the messages. Becoming dormant is the exception: the `Onboarding` row stays, so the dormancy step deletes that member's message rows explicitly. Rectification and rejoining never delete message rows. + +SQLite only enforces foreign keys when they are switched on for the connection. A test must confirm the cascade really fires with the better-sqlite3 adapter in use, and the schema must not rely on it silently doing nothing. Storing the messages as a JSON column on `Onboarding` was considered and rejected; see section 4. @@ -95,7 +98,7 @@ Evaluation order inside the pure function: 2. **Finished role present**: remove any leftover 7-day and messages roles, stop tracking. 3. **Joined before the launch date**: add start, finished and legacy. 4. **Dormant role present**: nothing until rectification. -5. **3 months past the anchor**: remove 7-day and messages, add dormant. +5. **3 months past the anchor**: remove 7-day and messages, add dormant, delete the member's message rows. 6. **7 days past the anchor**: add 7-day. 7. **400 counted characters**: add messages. 8. **Start, 7-day and messages all held**: add finished, remove 7-day and messages. @@ -104,12 +107,12 @@ Role changes run one at a time, as the existing [addRoleToUser](../../src/util/a ### Entry points -- **Join message**: create the tracking row, or reset it if the member is already tracked with an earlier join date. Handling is idempotent so replays are safe. +- **Join message**: create the member's `Onboarding` row. If a row already exists, nothing happens, whether the join message is a replay or the member left and rejoined before the daily job noticed (for example after a technical glitch). The row keeps its original join date and restart anchor, so a member who had been in the server for 6 days does not restart their 7-day clock, and the message rows are untouched. The roles lost while away are re-granted by the next evaluation. - **Any guild message from a member who is not exempt and not finished**: if there is no tracking row, create one from `message.member.joinedAt`. Record the counted length (messages before the restart anchor are ignored), then evaluate. - **Message edit and delete events**: recompute or remove the message record, then evaluate. - **Daily job**: scheduled with `node-cron` at a fixed UTC time, like the showcase digest. For each tracked member it fetches the member by ID. If the member is gone, their data is deleted. Otherwise it re-evaluates them. - **Startup catch-up**: fetch recent history of the system channel and replay join messages the bot missed while offline. -- **Rectification**: a shared function checks the member holds the dormant role (throws if not), removes it, sets the anchor to now, deletes the message records and evaluates. The `/onboarding reactivate` slash command and the dormant-channel button both call it. +- **Rectification**: a shared function checks the member holds the dormant role (throws if not), removes it, sets the anchor to now and evaluates. It does not touch message rows. The `/onboarding reactivate` slash command and the dormant-channel button both call it. ### Dormant channel From b92f9b99d06ab3b15c7ae7564ca03262a08617d4 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Sun, 1 Nov 2026 02:26:34 +0100 Subject: [PATCH 03/16] chore: state problem and goal mor eexplicitly --- ...onboarding-without-guild-members-intent.md | 109 +++++++++++++----- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.md b/docs/adr/0001-onboarding-without-guild-members-intent.md index ca4b2da..2393d7e 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.md @@ -6,23 +6,44 @@ status: accepted Terms in **bold** are defined in [GLOSSARY.md](../../GLOSSARY.md). +## Problem and goals + +Regulars have said they are tired of dealing with low-quality content from new joiners every day: badly formed help requests, and "look at my site" posts for projects that are fully vibe coded. They would prefer a calmer environment, with newcomers kept out of general chat and given the lounge instead. The trigger for this work is low-effort help requests, not a specific spam incident. + +Onboarding exists to serve these goals, in this order of weight: + +1. **Protect regulars' attention.** Keep low-effort questions and self-promotion out of the busiest channels until a newcomer has had time to settle in. +2. **Server comprehension.** Newcomers should have seen the rules and learned how to ask a good question before they reach the whole server. +3. **Spam and raids.** Fresh accounts have limited reach while trust is unknown. +4. **Drive-by accounts.** People who join, post once and leave should not cost moderators time. +5. **Limit hijacked dormant accounts.** An account that has never taken part for months can later be compromised and used to post scam messages. Dormancy removes the ability to write from accounts that have shown no sign of life. +6. **Encourage participation.** Newcomers get a friendly place to take part straight away (the lounge and a few topic channels) and a path to the rest. + +Reading is not restricted by this flow. Members who read but rarely post keep their access to read the server. Onboarding gates where a member can write, not whether they may be here. A dormant member can read but cannot write until they reactivate. + +### What the gates do and do not do + +The 7-day wait and the 400-character threshold are deliberate friction. They keep newcomers out of general chat and the rest of the server while they settle in, and they cost a drive-by account some effort. They do not judge quality. A member can pass them with filler, and a newcomer can still post a low-effort question or project in the channels the start role opens, including help forum and showcase. Moderation and the community's own norms remain responsible for quality. Onboarding only limits how much of the server a newcomer can reach while that happens. + +Every gate in section 1 must trace back to one of these goals. A gate that serves none of them should be removed. + ## 1. What Onboarding is New members of the Web Dev & Design server start with very little access and earn the rest of the server over time. **Onboarding** is the bot's own flow for this. It is separate from **Discord onboarding**, the question Discord shows at join, where the member picks a role such as Developer or Designer. -The purpose is to let newcomers take part in a few friendly channels straight away, while the rest of the server is held back until they have shown they are present and engaged. Members who never come back lose access again. +The purpose is to let newcomers take part in a few friendly channels straight away, while the rest of the server is held back until they have shown they are present and engaged. Members who never finish lose the ability to write again, though they can still read. ### The journey -| Stage | How a member gets there | What they can do | -| ---------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Everyone** | Joined, but has no onboarding role (for example skipped Discord onboarding) | Write in lounge, read a few channels, read message mods to open a ticket | -| **Start role** | Granted by Discord onboarding | Also write in help forum, ai, gaming, real life, frontend, design, backend and showcase | -| **7-day role** | 7 days after the **restart anchor** | Milestone only | -| **Messages role** | 400 **counted message** characters since the restart anchor | Milestone only | -| **Finished role** | Holds start, 7-day and messages | Full server access. Permanent. The 7-day and messages roles are removed | -| **Dormant role** | 3 months after the restart anchor without the finished role | Loses access to everything except message mods and the **dormant channel**. The 7-day and messages roles are removed; the start role stays | -| **Legacy member** | Joined before launch, first seen posting | Receives start, finished and **legacy role** at once | +| Stage | How a member gets there | What they can do | +| ----------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **Everyone** | Joined, but has no onboarding role (for example skipped Discord onboarding) | Write in lounge, read a few channels, read message mods to open a ticket | +| **Start role** | Granted by Discord onboarding | Also write in help forum, ai, gaming, real life, frontend, design, backend and showcase | +| **7-day role** | 7 days after the **restart anchor** | Milestone only | +| **Messages role** | 400 **counted message** characters since the restart anchor | Milestone only | +| **Finished role** | Holds start, 7-day and messages | Full server access. Permanent. The 7-day and messages roles are removed | +| **Dormant role** | 3 months after the restart anchor without the finished role | Loses access to everything except message mods and the **dormant channel**. The 7-day and messages roles are removed; the start role stays | +| **Legacy member** | Joined before launch, first seen posting | Receives start, finished and **legacy role** at once | ### Rules @@ -30,8 +51,9 @@ The purpose is to let newcomers take part in a few friendly channels straight aw - Dormancy applies to anyone without the finished role, including members who never held the start role. - Finishing needs the start role. A member could meet the 7-day and message conditions without it, but the finished role waits until Discord onboarding has been completed. - Counted messages are text only, with whitespace, markdown syntax and links removed. Attachments add nothing. Messages written before the start role count. Messages that are deleted, or removed by spam detection, do not. Edits recount the message. -- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role and set a new restart anchor. The message count is already empty, because it is cleared when the member becomes dormant. -- A member who leaves loses their Onboarding progress the next time the daily job runs. If they rejoin before that run, nothing changes: their restart anchor and counted messages stay as they were. If they rejoin after it, they start Onboarding again, even if they had finished before. Counted messages are only deleted in two cases: their onboarding record is deleted (they finished, or the daily job found they had left), or they become dormant. +- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role and set a new restart anchor. The message count is already empty, because it is cleared when the member becomes dormant. A returning member starts over on purpose, with another 7 days and 400 characters: the point is to show they are present again. +- The messages role is held only while the counted characters stay at or above the threshold. If deletions or spam removals drop a member below it before they finish, the role is removed, so posting 400 characters and deleting them does not count. +- A member who leaves keeps their Onboarding progress for a grace period of 7 days, counted from when the daily job first notices they are gone. If they rejoin within it, their restart anchor and counted messages are as they were, however soon after leaving they return. The time away counts toward the 7 days and the dormancy deadline, because the anchor does not move. After the grace period the progress is deleted and they start Onboarding again. Members who had already finished always start again, because their record is gone and Discord removes their roles when they leave. Counted messages are only deleted in two cases: their onboarding record is deleted (they finished, or the grace period after leaving ran out), or they become dormant. - Bots and moderators (**exempt members**) are never touched. - Which channels each role can see or write in is configured by moderators in Discord. The bot only adds and removes roles. It never edits channel permission overwrites. @@ -49,22 +71,22 @@ The intent is the obvious route. It delivers join, leave and role-update events - Leaves are noticed late, at the next daily run. - Time-based roles (7-day, dormant) are applied once a day, so they can arrive up to a day late. - Existing members can't be listed, so they become **legacy members** one at a time, as they post. -- A silent member whose join message was missed is invisible to the daily run and will never go dormant. -- Whether a leave resets progress depends on timing. A member who leaves and rejoins before the daily job runs keeps their anchor and counted messages, while one who rejoins after the job has deleted their row starts from zero. That follows from detecting leaves only in the daily job. +- Dormancy is best-effort. It only covers members the bot saw join or post. A silent member whose join message was missed is invisible to the daily run, and a legacy member who never posts is never evaluated, so neither goes dormant and both stay exposed to the hijacked-account risk in the goals. This is accepted. +- The leave grace period starts when the daily job notices the leave, not when the member left, so it can run up to a day longer than 7 days. ## 3. Technical design ### Signals the bot uses -| Need | Source | Needs privileged intent | -| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| A member joined | System join message (`MessageType.UserJoin`) in the server's system channel. `message.author` is the joiner | No (GuildMessages) | -| Join date, roles | `message.member` on every guild message (`joinedAt`, `roles`) | No | -| Message counted | `MessageCreate` | No | -| Message edited | `MessageUpdate` | No | -| Message uncounted | `MessageDelete` and `MessageDeleteBulk` | No | -| Member left | Daily run fetches each tracked member by ID and treats Discord's "Unknown Member" error (code 10007) as a leave | No | -| Start role granted | Seen on the member's next message, or when the daily run fetches them | No | +| Need | Source | Needs privileged intent | +| ------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------- | +| A member joined | System join message (`MessageType.UserJoin`) in the server's system channel. `message.author` is the joiner | No (GuildMessages) | +| Join date, roles | `message.member` on every guild message (`joinedAt`, `roles`) | No | +| Message counted | `MessageCreate` | No | +| Message edited | `MessageUpdate` | No | +| Message uncounted | `MessageDelete` and `MessageDeleteBulk` | No | +| Member left | Daily run fetches each tracked member by ID and treats Discord's "Unknown Member" error (code 10007) as a leave | No | +| Start role granted | Seen on the member's next message, or when the daily run fetches them | No | Spam detection removes messages with `bulkDelete`. discord.js reports that with a separate bulk-delete event, and the bot has no delete listeners today. Both events must be handled, otherwise spam would still count toward the 400 characters. @@ -72,7 +94,7 @@ Spam detection removes messages with `bulkDelete`. discord.js reports that with Two new Prisma models in [schema.prisma](../../prisma/schema.prisma): -- **Onboarding**: one row per member who is in progress or dormant. Columns are the user ID (primary key), join date and restart anchor. The row is deleted when the member finishes or leaves. +- **Onboarding**: one row per member who is in progress or dormant. Columns are the user ID (primary key), join date, restart anchor, and the time the bot first noticed the member had left (empty while they are in the server). The row is deleted when the member finishes, or when the grace period after leaving has ended. - **OnboardingMessage**: one row per counted message. Columns are the message ID (primary key), the user ID (a foreign key to `Onboarding` that cascades on delete) and the counted length. A member's total is the sum of their rows. A message's creation time is not stored: messages before the anchor are filtered when they arrive, and the time is encoded in the message ID if it is ever needed. A member's rows are deleted in exactly two cases: their `Onboarding` row is deleted, or they become dormant. Roles are not mirrored in the database. Discord's role list is the source of truth for the current stage, and the tables hold only what Discord cannot tell us (the anchor and the message lengths). @@ -100,17 +122,17 @@ Evaluation order inside the pure function: 4. **Dormant role present**: nothing until rectification. 5. **3 months past the anchor**: remove 7-day and messages, add dormant, delete the member's message rows. 6. **7 days past the anchor**: add 7-day. -7. **400 counted characters**: add messages. +7. **400 counted characters**: add messages. Below 400 while holding the messages role: remove it. 8. **Start, 7-day and messages all held**: add finished, remove 7-day and messages. Role changes run one at a time, as the existing [addRoleToUser](../../src/util/add-role-to-user.ts) does, so a large first run stays inside Discord's rate limits. Functions throw when they cannot complete their job and the caller handles it. ### Entry points -- **Join message**: create the member's `Onboarding` row. If a row already exists, nothing happens, whether the join message is a replay or the member left and rejoined before the daily job noticed (for example after a technical glitch). The row keeps its original join date and restart anchor, so a member who had been in the server for 6 days does not restart their 7-day clock, and the message rows are untouched. The roles lost while away are re-granted by the next evaluation. +- **Join message**: create the member's `Onboarding` row. If a row already exists, the bot clears its left time and changes nothing else, whether the join message is a replay or the member is returning within the grace period. The row keeps its original join date and restart anchor, so a member who had been in the server for 6 days does not restart their 7-day clock, and the message rows are untouched. The roles lost while away are re-granted by the next evaluation. - **Any guild message from a member who is not exempt and not finished**: if there is no tracking row, create one from `message.member.joinedAt`. Record the counted length (messages before the restart anchor are ignored), then evaluate. - **Message edit and delete events**: recompute or remove the message record, then evaluate. -- **Daily job**: scheduled with `node-cron` at a fixed UTC time, like the showcase digest. For each tracked member it fetches the member by ID. If the member is gone, their data is deleted. Otherwise it re-evaluates them. +- **Daily job**: scheduled with `node-cron` at a fixed UTC time, like the showcase digest. For each tracked member it fetches the member by ID. If the member is gone, it records when it first noticed, and deletes their data once the grace period has passed; members marked as left are not evaluated. If the member is present, it clears any recorded left time and re-evaluates them. - **Startup catch-up**: fetch recent history of the system channel and replay join messages the bot missed while offline. - **Rectification**: a shared function checks the member holds the dormant role (throws if not), removes it, sets the anchor to now and evaluates. It does not touch message rows. The `/onboarding reactivate` slash command and the dormant-channel button both call it. @@ -120,7 +142,7 @@ At startup the bot makes sure one fixed message with a "Reactivate" button exist ### Configuration -- **Constants** in one onboarding config file: counted-character threshold (400), days until the 7-day role (7) and dormancy period (3 months), daily-job time, launch-date constant. +- **Constants** in one onboarding config file: counted-character threshold (400), days until the 7-day role (7) and dormancy period (3 months), leave grace period (7 days), daily-job time, launch-date constant. - **Required env vars** in [env.ts](../../src/env.ts): the five onboarding role IDs plus the legacy role ID, the dormant channel ID and the onboarding log channel ID, and the launch date. The optional `ONBOARDING_ROLE_ID` and `ONBOARDING_CHANNEL_ID` and the unused `/onboarding` stub are removed. - **Permissions**: the bot's own role must sit above all onboarding roles and have Manage Roles, and it needs View Channel on the system channel. - The GuildMembers intent stays off. No change to the intents in [index.ts](../../src/index.ts). @@ -130,6 +152,7 @@ At startup the bot makes sure one fixed message with a "Reactivate" button exist - **Apply for the GuildMembers intent.** Simplest design. It would also give events for role changes and leaves, and a list of all members for a clean backfill. Rejected because it depends on Discord's approval, which has been needed before and is not ours to decide. - **Keep a running character total per member instead of per-message records.** Less storage. Rejected because deleted and spam-removed messages must stop counting, and a total alone cannot tell which characters to subtract. - **One table, with the per-message lengths in a JSON column on `Onboarding`** (message ID mapped to counted length). Simpler schema, one read for a member's whole state, and one delete for cleanup. Rejected for three reasons. Every update is a read-modify-write of the whole column, so two events for the same member, such as a new message during a spam bulk-delete, can silently overwrite each other unless updates are serialised per member. A delete event for an uncached message gives only the message ID, so finding the member who owns it means scanning every member's JSON with raw SQL, since Prisma cannot filter inside JSON on SQLite as far as we know. And the total would be computed in code, with the whole blob rewritten on every message. Fixing the first two with a per-member lock and a message-to-member lookup would rebuild the second table in a worse form. +- **Restore finished members who leave and rejoin.** Would need a permanent record of every finished member. Without the intent the bot only learns of a rejoin, never of the leave, so the 7-day grace period given to in-progress members cannot be applied: the choices are restoring access whenever they return, or guessing from the finish time. Rejected to keep the design small and not grant full access automatically; a finished member who returns starts again. - **Hourly job instead of daily.** Faster role delivery. Not chosen: a daily job was the requirement and the lag is acceptable. - **Bot manages channel permission overwrites.** Would let the bot enforce what "dormant" means. Rejected because channel permissions belong to moderators and the onboarding flow is meant to be roles only. @@ -137,4 +160,32 @@ At startup the bot makes sure one fixed message with a "Reactivate" button exist - That the bot can see and read the system join messages in the real server, and that welcome messages are enabled there. - That fetching a single member by ID works with the intent off. Discord's docs say only List Guild Members requires the intent. The Get Guild Member page does not mention one, and the existing stub in [features/onboarding](../../src/features/onboarding/index.ts) already calls it. -- Whether the `messages` role should be revoked if later deletions drop a member below the threshold before they finish. The current design reconciles in that direction, but it needs a decision. +- How a dormant member is made read-only. Decision pending; the two options are described below. + +### Open decision: making dormant members read-only + +A dormant member must be able to read the server but not write anywhere, including the lounge. The lounge stays open to every other member. Two ways to get there. + +**Option A: channel permissions.** Dormancy removes the start role. Moderators configure the dormant role with read access everywhere and a Send Messages deny on every writable channel, the lounge included. Discord applies `@everyone` overwrites first and role overwrites after, so the dormant role's deny beats the lounge's `@everyone` allow. Another role's explicit allow would beat the deny, because Discord combines role overwrites so that an allow from any role wins over a deny from another. The start role's write allows are such an allow, which is why the start role has to be removed. Reactivation re-adds it. + +- Discord enforces it. Nothing is posted, and it works when the bot is down. +- The bot still only moves roles and never edits overwrites. +- The member cannot write anywhere and gets no prompt on their own. They may not know why, or how to come back, unless they are told (see "Telling the member" below). +- Reactivation skips Discord onboarding the second time. + +**Option B: delete and notify.** Dormant members keep their channel permissions. When one writes a message, the bot deletes the message and tells them to press a reactivate button. This gives the member a clear, immediate task without searching the server. + +- Discord only allows an ephemeral message as the response to an interaction (a slash command or button press). The bot cannot send one in reply to a normal message. The message that carries the button is therefore a DM, which fails if the member has DMs closed, a visible reply that deletes itself, or a private thread (see "Telling the member" below). Pressing the button can then respond ephemerally. +- The offending message is posted before it is deleted. It can ping people and unfurl links for the moment it exists, and it stays if the bot is down or rate-limited. This weakens the hijacked-account goal, since a compromised account can post a scam before the delete lands. +- The bot needs the Manage Messages permission and must act on every guild message from a dormant member. +- Every dormant member who writes is deleted and notified, so a member with a legitimate message sees it removed. + +### Telling the member + +A dormant member should be told what happened and given one action to take, without searching the server. A private thread does this better than a DM: it does not fail when DMs are closed, the message stays inside the server, and the reactivate button sits next to the explanation. When the member becomes dormant (or, in option B, when they first write), the bot creates a private thread, adds the member, and posts the explanation and the reactivate button. The member is notified because they were added to the thread. + +This works with either option and replaces the single shared button message in the dormant channel, or sits beside it. To verify before relying on it: + +- A member added to a private thread can see and use it when the parent channel is otherwise hidden from them. +- The bot has permission to create private threads and add members. +- The number of threads stays manageable, and an archived thread can still be used. Threads should be deleted or reused on reactivation. From f0908d3183073b17b55f2ab16b0e9a354bedd7db Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 14:48:05 +0100 Subject: [PATCH 04/16] chore: capture review findings for ADR 0001 in scratchpad Record blockers, logic bugs, technical gaps and structure concerns from the harsh review of the onboarding ADR as open items; no decisions made yet and the main document is untouched. Co-Authored-By: Claude Sonnet 5.5 --- ...ing-without-guild-members-intent.claude.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/adr/0001-onboarding-without-guild-members-intent.claude.md diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md new file mode 100644 index 0000000..a599068 --- /dev/null +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -0,0 +1,47 @@ +# Scratchpad: ADR 0001 (onboarding without GuildMembers intent) + +Main doc: `0001-onboarding-without-guild-members-intent.md`. Status in main doc is `accepted`; review says it should be `proposed`. + +## Session 1 state + +Harsh review done (in chat). Nothing in the main doc edited yet. All findings below are OPEN until the human decides. + +## Open findings (need human decisions) + +### Blockers +- B1 Intent rationale self-contradicts: bot already depends on MessageContent (privileged, `src/index.ts:17`); the 400-char mechanic needs it. "Approval could be refused/revoked" applies equally. Also no evidence anyone applied for GuildMembers and was refused. 10,000-user claim unverified. Q: has GuildMembers been applied for? What is the guild/bot size? What is the real reason to avoid the intent? +- B2 Goal 1 (protect regulars) is defeated: start role (granted at join by Discord onboarding) opens help forum + showcase, the exact channels that caused the problem. Q: move those behind finished role, or reword goal 1? +- B3 Status `accepted` but section 5 has open decision + unverified assumptions. Should be `proposed`. +- B4 Contradictions: line 22/34 say dormant can read; journey table line 45 says dormant loses access to everything except message mods + dormant channel. Table says "start role stays"; Option A removes start role. Rectification text and eval-order step 5 silent on start role. + +### Logic bugs +- L5 Messages posted while dormant (dormant channel, Option B) are after the old anchor so get counted; rectification does not delete rows, contradicting "count is already empty". +- L6 Dormancy rule (3 months after anchor w/o finished) does not match goal 5 (never-taken-part accounts). Hits slow active members and active lounge chatters who skipped Discord onboarding (cannot finish without start role). Never touches finished members (real hijack targets). Legacy non-posters invisible at launch. +- L7 Leave grace loophole: kick + rejoin within 7 days keeps progress; time away counts toward the 7 days. +- L8 Launch date listed as both constant (line 145) and env var (line 146). + +### Technical gaps +- T9 No `Partials` in `src/index.ts`; discord.js will not emit messageDelete/messageUpdate for uncached messages, and messageDeleteBulk drops uncached ones. ADR relies on "ID-only delete event". +- T10 Deletes/edits while bot offline are lost; counts drift forever; startup catch-up only replays join messages. +- T11 No estimate of tracked-member count N; daily job does N fetches; dormant rows never expire. +- T12 Read-only needs more than Send Messages deny (threads, forum posts, reactions, voice, attachments); new channels do not inherit. +- T13 Concurrency argument against JSON column applied selectively; evaluate() is also read-modify-write and step 5 deletes rows while inserts may race. + +### Structure +- S14 ADR contains a full spec (journey table, rules, schema, eval order, env vars). Q: split spec out of the ADR into a separate doc? +- S15 Option B is a strawman (defeats goal 5 by its own text); recommend closing on Option A. "Telling the member" private-thread section is scope creep; candidate for follow-up issue. + +### Minor +- Who accepted dormancy gap (line 74) and under what conditions it becomes unacceptable? +- No stated path for "Everyone" members to get start role. +- "a daily job was the requirement": cite source. + +## Proposed section order (not agreed yet) +1. Status + Problem/goals (B2, B3, L6 goal 5 wording) +2. Decision / why not the intent (B1) +3. Journey table + rules (B4, L5, L7, L8) +4. Technical design (T9-T13) +5. Open decision: dormant read-only (S15, T12) + +## Decisions made +None yet. From 7979489e28f9d4ce6ab1613703099c4c057d30eb Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 14:51:40 +0100 Subject: [PATCH 05/16] chore: record decision to drop ADR 0001 status Human decided the ADR carries no status or front matter; B3 marked resolved and Q2-Q4 (goal 1 channels, goal 5 fate, section order/spec split) still pending. Co-Authored-By: Claude Sonnet 5.5 --- ...-onboarding-without-guild-members-intent.claude.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index a599068..8ee812a 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -1,6 +1,6 @@ # Scratchpad: ADR 0001 (onboarding without GuildMembers intent) -Main doc: `0001-onboarding-without-guild-members-intent.md`. Status in main doc is `accepted`; review says it should be `proposed`. +Main doc: `0001-onboarding-without-guild-members-intent.md`. Front matter (status) removed from main doc at the human's direction: the ADR carries no status at all. ## Session 1 state @@ -11,7 +11,7 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be ### Blockers - B1 Intent rationale self-contradicts: bot already depends on MessageContent (privileged, `src/index.ts:17`); the 400-char mechanic needs it. "Approval could be refused/revoked" applies equally. Also no evidence anyone applied for GuildMembers and was refused. 10,000-user claim unverified. Q: has GuildMembers been applied for? What is the guild/bot size? What is the real reason to avoid the intent? - B2 Goal 1 (protect regulars) is defeated: start role (granted at join by Discord onboarding) opens help forum + showcase, the exact channels that caused the problem. Q: move those behind finished role, or reword goal 1? -- B3 Status `accepted` but section 5 has open decision + unverified assumptions. Should be `proposed`. +- B3 RESOLVED: status/front matter removed entirely (human: "it should have no status at all"). Section 5 open items still stand on their own. - B4 Contradictions: line 22/34 say dormant can read; journey table line 45 says dormant loses access to everything except message mods + dormant channel. Table says "start role stays"; Option A removes start role. Rectification text and eval-order step 5 silent on start role. ### Logic bugs @@ -44,4 +44,9 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be 5. Open decision: dormant read-only (S15, T12) ## Decisions made -None yet. +- ADR has no status and no YAML front matter. Done in main doc. + +## Still waiting on human (asked end of session 1) +- Q2 goal 1 vs start role channels (a/b/c) +- Q3 goal 5 fate (a/b/c) +- Q4 section order; and whether to split the spec out of the ADR From 3c83437b2f0fff7bf69225a783d5627841214dfe Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 14:57:49 +0100 Subject: [PATCH 06/16] chore: capture Q2 showcase facts and Q3 activity-based dormancy decision Showcase creation is bot-button gated so start role only comments; dormancy moves to a lastActivity column (messages, forum posts, voice join). Pending: finished-member scope, voice semantics, help forum posting rights. Co-Authored-By: Claude Sonnet 5.5 --- ...rding-without-guild-members-intent.claude.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 8ee812a..90a478a 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -15,6 +15,7 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be - B4 Contradictions: line 22/34 say dormant can read; journey table line 45 says dormant loses access to everything except message mods + dormant channel. Table says "start role stays"; Option A removes start role. Rectification text and eval-order step 5 silent on start role. ### Logic bugs +- (Q3 decided: dormancy now activity-based, see Decisions; L6 largely addressed except finished-member scope and legacy non-posters) - L5 Messages posted while dormant (dormant channel, Option B) are after the old anchor so get counted; rectification does not delete rows, contradicting "count is already empty". - L6 Dormancy rule (3 months after anchor w/o finished) does not match goal 5 (never-taken-part accounts). Hits slow active members and active lounge chatters who skipped Discord onboarding (cannot finish without start role). Never touches finished members (real hijack targets). Legacy non-posters invisible at launch. - L7 Leave grace loophole: kick + rejoin within 7 days keeps progress; time away counts toward the 7 days. @@ -46,7 +47,17 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be ## Decisions made - ADR has no status and no YAML front matter. Done in main doc. -## Still waiting on human (asked end of session 1) -- Q2 goal 1 vs start role channels (a/b/c) -- Q3 goal 5 fate (a/b/c) +- Q2 (goal 1 vs start role channels), human's facts: new joiners with the start role can COMMENT on showcases in the showcase forum but cannot CREATE showcases. Creation goes through the bot via a button in showcase-guidelines, which the start role cannot reach. Start role can "participate" in the help forum. So the journey table line "write in ... showcase" is inaccurate; must say comment-only. Reasoning: showcase spam is already blocked structurally; the remaining question is help forum posting. +- Q3 (goal 5): dormancy becomes ACTIVITY-based. Store `lastActivity` datetime in DB. Activity = wrote a message anywhere, created a forum post, joined and talked in voice. Each updates lastActivity. Dormant after 3 months with no activity. Replaces "3 months after restart anchor without finished". Reasoning: dormancy should target accounts with no sign of life, not slow members. + +## Implications of Q3 to resolve (asked via AskUserQuestion) +- Does activity dormancy apply to finished members too? Today: finished => row deleted, permanent. Covering finished means keeping a row (lastActivity) for every finished member, which reverses "row deleted on finish" and affects the leave/rejoin design and the no-intent tracking (finished members only seen when they post/join voice). +- Voice: bot cannot detect "talking", only joining (VoiceStateUpdate, GuildVoiceStates already enabled). +- Write volume: updating lastActivity on every message; probably throttle (only write if older than N hours). +- Initial lastActivity for a new row = join date; rectification sets lastActivity = now. +- Interaction with message-count bug L5: dormant-period messages are activity AND get counted; still needs fixing. +- Eval order step 5 and journey table dormant row need rewording once decided. + +## Still waiting on human - Q4 section order; and whether to split the spec out of the ADR +- Help forum: can start role CREATE posts or only reply? (matters for goal 1) From 9225547afcbc2c8970a76e5939cf00fb8fd90ecd Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:00:19 +0100 Subject: [PATCH 07/16] chore: record help forum, dormancy scope, voice and throttle decisions Start role can create help posts (trigger not addressed by gates); dormancy covers only unfinished members; voice join counts; lastActivity throttled. Problem-and-goals draft is in the main doc awaiting review. Co-Authored-By: Claude Sonnet 5.5 --- ...onboarding-without-guild-members-intent.claude.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 90a478a..15384a1 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -58,6 +58,16 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be - Interaction with message-count bug L5: dormant-period messages are activity AND get counted; still needs fixing. - Eval order step 5 and journey table dormant row need rewording once decided. +## Decisions from AskUserQuestion round (session 1) +- Help forum: start role can CREATE and reply. So goal 1 cannot claim to stop low-effort help requests; draft rewords it and the "do not do" paragraph says so. Flag to human: this means the original trigger (low-effort help requests) is NOT addressed by this ADR. Ask whether that is intended. +- Dormancy scope: ONLY unfinished members. Finished stay permanent and row is still deleted on finish. Consequence: hijack goal covers only never-finished accounts (stated in goal 5 draft). Human chose this over recommended "everyone". +- Voice activity = joining a voice channel (VoiceStateUpdate). +- lastActivity writes throttled (only update when stored value older than an interval, e.g. 1 day). Exact interval to settle in technical design. + +## Drafted in main doc (awaiting human review): Problem and goals +- Goal 1 reworded, goal 5 reworded, "what the gates do" paragraph shortened to help forum only. +- Not yet touched: journey table (still says showcase write; must become comment-only), dormant row, eval order step 5, Data section (lastActivity column), rectification (sets lastActivity), L5 etc. + ## Still waiting on human - Q4 section order; and whether to split the spec out of the ADR -- Help forum: can start role CREATE posts or only reply? (matters for goal 1) +- Review of the drafted Problem and goals section From 90147a2babeea5fec5aa4c9864e8e81bcd1f3fe9 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:10:31 +0100 Subject: [PATCH 08/16] chore: capture journey-section decisions and drafted rule changes Dormant uses delete-and-notify (Option B, chosen over recommendation A); dormant messages not counted; time away pauses anchor and lastActivity on rejoin. Lists untouched downstream sections and the unformatted-table caveat. Co-Authored-By: Claude Sonnet 5.5 --- ...ing-without-guild-members-intent.claude.md | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 15384a1..567caca 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -64,10 +64,29 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be - Voice activity = joining a voice channel (VoiceStateUpdate). - lastActivity writes throttled (only update when stored value older than an interval, e.g. 1 day). Exact interval to settle in technical design. -## Drafted in main doc (awaiting human review): Problem and goals -- Goal 1 reworded, goal 5 reworded, "what the gates do" paragraph shortened to help forum only. -- Not yet touched: journey table (still says showcase write; must become comment-only), dormant row, eval order step 5, Data section (lastActivity column), rectification (sets lastActivity), L5 etc. +## FINALIZED by human: Problem and goals (goals 1 and 5, gates paragraph) +- Human on the trigger gap: "low effort or low quality messages should not pollute the main area where people hang out, notably general chat. this is why all new comers only have access to the lounge." Intended scope of goal 1 = protect GENERAL CHAT. Trigger paragraph left as is. +- Human on stages: "people without any roles only have access to lounge, then the start role gives access to more channels, and after finishing the onboarding they gain access to the full server." So the journey table channel list is right; just showcase is comment-only. + +## Decisions (journey section round) +- Dormant = Option B (delete and notify). Human chose it over my recommendation A. Known cost, recorded in ADR already: message is visible briefly, weakens goal 5, bot needs Manage Messages and must handle every message from dormant members. Option A text and S15/T12 now partly moot; Option A can be kept in "considered options" instead of "open decision". Start role stays on dormancy (matches table). +- Messages from dormant members are NOT counted (skip recording while dormant role held). Resolves L5. +- Time away does NOT count on rejoin within grace: anchor AND lastActivity shift forward by away time. Human chose this. Away time measured from when the daily job noticed the leave (imprecise by up to a day, consistent with grace period). Resolves L7. Needs stored state: Onboarding row already has "left noticed" time; shift on rejoin. + +## Drafted in main doc (awaiting human review): Journey table and rules +- Start-role row: showcase comment-only, help forum create+reply. +- Dormant row: 3 months without activity; keeps channel access, messages deleted + notified. +- Rules: activity definition (message anywhere incl. forum post, voice join; throttled; initial = join date), dormancy runs from lastActivity, finished never dormant, rectification sets lastActivity=now and dormant messages not counted, rejoin shifts anchor+lastActivity. +- Formatting NOT run: `pnpm exec oxfmt` fails on Node v20.17 (needs newer; .nvmrc). Table padding is unformatted; run fmt before commit of main doc. + +## Not yet touched (later sections) +- Section 2/5: Option A/B open-decision section needs rewriting to reflect B chosen. "Telling the member" thread section now relevant to B (DM vs ephemeral vs private thread: still undecided). +- Section 3: Data (add lastActivity column on Onboarding), evaluation order step 5 (dormancy by lastActivity, not anchor), join-message entry point ("changes nothing else" now shifts anchor/lastActivity), message entry point (skip recording when dormant, update lastActivity incl. voice and forum posts), new VoiceStateUpdate entry point, Option B handler (delete messages from dormant members, Manage Messages permission), daily job wording. +- Verify: forum post starter message arrives as MessageCreate; VoiceStateUpdate available. +- L8 launch date in both constants and env vars: still open, decide which. +- B1 (intent argument), T9-T13, S14 (split spec) still open. ## Still waiting on human -- Q4 section order; and whether to split the spec out of the ADR -- Review of the drafted Problem and goals section +- Review of drafted Journey table and rules +- Launch date: constant or env var (L8) +- Q4: whether to split spec out of ADR (deferred; human chose to edit in place first) From f892371f56b6e4b7fe79fafe3d0855192cc3d077 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:26:45 +0100 Subject: [PATCH 09/16] chore: scope work to business definition; park technical sections Human wants Problem/goals and the journey+rules settled before any technical design edits. Listed open business questions and parked the technical items. Co-Authored-By: Claude Sonnet 5.5 --- ...onboarding-without-guild-members-intent.claude.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 567caca..bbc2285 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -79,7 +79,17 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be - Rules: activity definition (message anywhere incl. forum post, voice join; throttled; initial = join date), dormancy runs from lastActivity, finished never dormant, rectification sets lastActivity=now and dormant messages not counted, rejoin shifts anchor+lastActivity. - Formatting NOT run: `pnpm exec oxfmt` fails on Node v20.17 (needs newer; .nvmrc). Table padding is unformatted; run fmt before commit of main doc. -## Not yet touched (later sections) +## SCOPE DECISION (human): business definition first, technical details later +Work only on "Problem and goals" and "1. What Onboarding is" (journey + rules) until the business definition is settled. Do NOT edit sections 2-5 (decision, technical design, considered options, open decision) yet. Technical items below are parked, not dropped. Business items still open are listed under "Business questions open". + +## Business questions open +- Review of drafted journey table and rules +- How is a dormant member told what happened (what the member experiences)? DM / reply / private thread: business-visible, decide wording only, mechanism later +- Path for "Everyone" (no start role) members to get the start role / are they nudged +- Why 7 days and 400 chars (trace each gate to a goal) +- Legacy member rule is business-visible: confirm + +## Not yet touched (later sections; PARKED until business definition done) - Section 2/5: Option A/B open-decision section needs rewriting to reflect B chosen. "Telling the member" thread section now relevant to B (DM vs ephemeral vs private thread: still undecided). - Section 3: Data (add lastActivity column on Onboarding), evaluation order step 5 (dormancy by lastActivity, not anchor), join-message entry point ("changes nothing else" now shifts anchor/lastActivity), message entry point (skip recording when dormant, update lastActivity incl. voice and forum posts), new VoiceStateUpdate entry point, Option B handler (delete messages from dormant members, Manage Messages permission), daily job wording. - Verify: forum post starter message arrives as MessageCreate; VoiceStateUpdate available. From b706ff8935a7eceaa79865ee47b4098f75f6e8c1 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:31:30 +0100 Subject: [PATCH 10/16] chore: record dormant notice, no-nudge and gate-reason decisions Journey and rules finalized. Dormant members get a private notice (private thread vs public reply left for the team). Goal 2 has no gate behind it; asked human. Co-Authored-By: Claude Sonnet 5.5 --- ...oarding-without-guild-members-intent.claude.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index bbc2285..a2ba757 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -82,12 +82,17 @@ Harsh review done (in chat). Nothing in the main doc edited yet. All findings be ## SCOPE DECISION (human): business definition first, technical details later Work only on "Problem and goals" and "1. What Onboarding is" (journey + rules) until the business definition is settled. Do NOT edit sections 2-5 (decision, technical design, considered options, open decision) yet. Technical items below are parked, not dropped. Business items still open are listed under "Business questions open". +## FINALIZED by human: journey table and rules (as drafted in session 1) + +## Business decisions (latest round) +- Dormant notice: member is told in PRIVATE (private thread leaning) immediately when their message is deleted, with a way to reactivate. Reasoning (human): a user who cannot write somewhere will more likely churn than investigate; informing them keeps them in the server and makes reactivating easy; a bot is less likely to react to such a message. OPEN FOR THE TEAM (not the human alone): private thread (member, bot, mods only) vs reply in the channel where everyone sees it. Written into ADR rules as an open point. +- No start role: nothing, their choice, no nudge. Written into rules. +- Gates: 7 days = friction for drive-bys (goals 3, 4). 400 chars = rough engagement sign (goal 6) AND friction; no quality judgement. Human did NOT pick "7 days: time to read rules", so goal 2 (server comprehension) is backed by no gate; only by Discord onboarding presumably. Check with human. + ## Business questions open -- Review of drafted journey table and rules -- How is a dormant member told what happened (what the member experiences)? DM / reply / private thread: business-visible, decide wording only, mechanism later -- Path for "Everyone" (no start role) members to get the start role / are they nudged -- Why 7 days and 400 chars (trace each gate to a goal) -- Legacy member rule is business-visible: confirm +- Goal 2 (comprehension) has no gate behind it: drop, reword, or attribute to Discord onboarding? +- Legacy member rule: confirm (joined before launch, receives start+finished+legacy on first post; never evaluated if they never post) +- Launch date: constant vs env var (L8), probably technical; park ## Not yet touched (later sections; PARKED until business definition done) - Section 2/5: Option A/B open-decision section needs rewriting to reflect B chosen. "Telling the member" thread section now relevant to B (DM vs ephemeral vs private thread: still undecided). From 17e95a4f6a236759a9a0008816ab8c1803797136 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:34:33 +0100 Subject: [PATCH 11/16] chore: clarify goal dormancy --- ...onboarding-without-guild-members-intent.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.md b/docs/adr/0001-onboarding-without-guild-members-intent.md index 2393d7e..7781b80 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.md @@ -1,7 +1,3 @@ ---- -status: accepted ---- - # Onboarding: a role-based member journey, built without the GuildMembers intent Terms in **bold** are defined in [GLOSSARY.md](../../GLOSSARY.md). @@ -12,18 +8,18 @@ Regulars have said they are tired of dealing with low-quality content from new j Onboarding exists to serve these goals, in this order of weight: -1. **Protect regulars' attention.** Keep low-effort questions and self-promotion out of the busiest channels until a newcomer has had time to settle in. +1. **Protect regulars' attention.** Keep newcomers out of general chat and the rest of the server until they have had time to settle in. Newcomers cannot create showcases, because that goes through a bot button in a channel they cannot reach. They can comment on showcases and take part in the help forum from the start. 2. **Server comprehension.** Newcomers should have seen the rules and learned how to ask a good question before they reach the whole server. 3. **Spam and raids.** Fresh accounts have limited reach while trust is unknown. 4. **Drive-by accounts.** People who join, post once and leave should not cost moderators time. -5. **Limit hijacked dormant accounts.** An account that has never taken part for months can later be compromised and used to post scam messages. Dormancy removes the ability to write from accounts that have shown no sign of life. +5. **Limit hijacked dormant accounts.** An account that has shown no activity for months can later be compromised and used to post scam messages. A member who has not finished Onboarding and has no activity for 3 months loses the ability to write. Activity means writing a message anywhere, creating a forum post, or joining a voice channel. Finished members are never made dormant, so this goal does not cover them. 6. **Encourage participation.** Newcomers get a friendly place to take part straight away (the lounge and a few topic channels) and a path to the rest. Reading is not restricted by this flow. Members who read but rarely post keep their access to read the server. Onboarding gates where a member can write, not whether they may be here. A dormant member can read but cannot write until they reactivate. ### What the gates do and do not do -The 7-day wait and the 400-character threshold are deliberate friction. They keep newcomers out of general chat and the rest of the server while they settle in, and they cost a drive-by account some effort. They do not judge quality. A member can pass them with filler, and a newcomer can still post a low-effort question or project in the channels the start role opens, including help forum and showcase. Moderation and the community's own norms remain responsible for quality. Onboarding only limits how much of the server a newcomer can reach while that happens. +The 7-day wait and the 400-character threshold are deliberate friction. They keep newcomers out of general chat and the rest of the server while they settle in, and they cost a drive-by account some effort. The 7 days make a throwaway account wait before it reaches everything. The 400 characters are a rough sign that the member has taken part, and also cost effort. They do not judge quality. A member can pass them with filler, and a newcomer can still create a low-effort help request in the help forum, which the start role opens. Moderation and the community's own norms remain responsible for quality. Onboarding only limits how much of the server a newcomer can reach while that happens. Every gate in section 1 must trace back to one of these goals. A gate that serves none of them should be removed. @@ -38,22 +34,25 @@ The purpose is to let newcomers take part in a few friendly channels straight aw | Stage | How a member gets there | What they can do | | ----------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **Everyone** | Joined, but has no onboarding role (for example skipped Discord onboarding) | Write in lounge, read a few channels, read message mods to open a ticket | -| **Start role** | Granted by Discord onboarding | Also write in help forum, ai, gaming, real life, frontend, design, backend and showcase | +| **Start role** | Granted by Discord onboarding | Also write in help forum (create and reply), ai, gaming, real life, frontend, design and backend, and comment on showcases. Showcases are created through a bot button in a channel this role cannot reach | | **7-day role** | 7 days after the **restart anchor** | Milestone only | | **Messages role** | 400 **counted message** characters since the restart anchor | Milestone only | | **Finished role** | Holds start, 7-day and messages | Full server access. Permanent. The 7-day and messages roles are removed | -| **Dormant role** | 3 months after the restart anchor without the finished role | Loses access to everything except message mods and the **dormant channel**. The 7-day and messages roles are removed; the start role stays | +| **Dormant role** | 3 months without **activity**, without the finished role | Keeps its channel access, but every message they write is deleted and they are told how to reactivate. The 7-day and messages roles are removed; the start role stays | | **Legacy member** | Joined before launch, first seen posting | Receives start, finished and **legacy role** at once | ### Rules -- The restart anchor is the later of the member's join date and their latest rectification. The 7-day clock, the message count and the 3-month dormancy deadline all run from it. -- Dormancy applies to anyone without the finished role, including members who never held the start role. +- The restart anchor is the later of the member's join date and their latest rectification. The 7-day clock and the message count run from it. +- **Activity** is writing a message anywhere (a forum post counts), or joining a voice channel. The bot cannot tell whether someone speaks, only that they joined. It stores the time of the member's last activity, and updates it only when the stored time is older than a set interval, because dormancy is measured in months. A new member's last activity starts as their join date. +- Dormancy runs from last activity: 3 months with none, for anyone without the finished role, including members who never held the start role. Finished members are never made dormant. +- When a dormant member writes, their message is deleted and the bot immediately tells them why, privately, and how to reactivate. A member who simply cannot write somewhere is more likely to leave than to investigate, and this keeps them in the server and makes reactivating easy. Open for the team: whether this happens in a private thread (only the member, the bot and moderators can see it) or as a reply in the channel itself, where everyone sees it. +- A member who has no start role (for example, skipped Discord onboarding) stays limited to the lounge until they complete Discord onboarding themselves. They are not nudged. - Finishing needs the start role. A member could meet the 7-day and message conditions without it, but the finished role waits until Discord onboarding has been completed. - Counted messages are text only, with whitespace, markdown syntax and links removed. Attachments add nothing. Messages written before the start role count. Messages that are deleted, or removed by spam detection, do not. Edits recount the message. -- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role and set a new restart anchor. The message count is already empty, because it is cleared when the member becomes dormant. A returning member starts over on purpose, with another 7 days and 400 characters: the point is to show they are present again. +- **Rectification** leaves dormancy. A moderator runs `/onboarding reactivate @member`, or the member presses the button in the dormant channel. Both remove the dormant role, set a new restart anchor and set last activity to now. The message count is already empty, because it is cleared when the member becomes dormant, and messages from a member holding the dormant role are not counted. A returning member starts over on purpose, with another 7 days and 400 characters: the point is to show they are present again. - The messages role is held only while the counted characters stay at or above the threshold. If deletions or spam removals drop a member below it before they finish, the role is removed, so posting 400 characters and deleting them does not count. -- A member who leaves keeps their Onboarding progress for a grace period of 7 days, counted from when the daily job first notices they are gone. If they rejoin within it, their restart anchor and counted messages are as they were, however soon after leaving they return. The time away counts toward the 7 days and the dormancy deadline, because the anchor does not move. After the grace period the progress is deleted and they start Onboarding again. Members who had already finished always start again, because their record is gone and Discord removes their roles when they leave. Counted messages are only deleted in two cases: their onboarding record is deleted (they finished, or the grace period after leaving ran out), or they become dormant. +- A member who leaves keeps their Onboarding progress for a grace period of 7 days, counted from when the daily job first notices they are gone. If they rejoin within it, their counted messages are as they were, however soon after leaving they return. The time away does not count toward the 7 days or the dormancy deadline: the restart anchor and last activity move forward by the time away, measured from when the daily job noticed the leave until the rejoin. After the grace period the progress is deleted and they start Onboarding again. Members who had already finished always start again, because their record is gone and Discord removes their roles when they leave. Counted messages are only deleted in two cases: their onboarding record is deleted (they finished, or the grace period after leaving ran out), or they become dormant. - Bots and moderators (**exempt members**) are never touched. - Which channels each role can see or write in is configured by moderators in Discord. The bot only adds and removes roles. It never edits channel permission overwrites. From 3fa91b000f275337630ae6a1d0c7f0aa4ced9298 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:35:56 +0100 Subject: [PATCH 12/16] chore: reword goal 2, confirm legacy rule; business definition near done Goal 2 now says the 7-day wait gives reading time without enforcement. Legacy rule confirmed. Remaining: sign-off on business half, then technical sections starting with the intent argument. Co-Authored-By: Claude Sonnet 5.5 --- ...1-onboarding-without-guild-members-intent.claude.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index a2ba757..fa0c02f 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -89,10 +89,14 @@ Work only on "Problem and goals" and "1. What Onboarding is" (journey + rules) u - No start role: nothing, their choice, no nudge. Written into rules. - Gates: 7 days = friction for drive-bys (goals 3, 4). 400 chars = rough engagement sign (goal 6) AND friction; no quality judgement. Human did NOT pick "7 days: time to read rules", so goal 2 (server comprehension) is backed by no gate; only by Discord onboarding presumably. Check with human. +## Business definition status: essentially complete +- Goal 2 reworded (human chose): the 7-day wait gives time to read rules and see good questions; nothing checks it. Drafted in main doc, awaiting a final look. +- Legacy rule confirmed as written (start+finished+legacy on first post). + ## Business questions open -- Goal 2 (comprehension) has no gate behind it: drop, reword, or attribute to Discord onboarding? -- Legacy member rule: confirm (joined before launch, receives start+finished+legacy on first post; never evaluated if they never post) -- Launch date: constant vs env var (L8), probably technical; park +- Team decision (not ours): private thread vs in-channel reply for dormant notice. Stays as open point in ADR rules. +- Launch date: constant vs env var (L8), technical; park +- Next: either human reads the whole business half once more and signs off, or we move to technical sections (B1 intent argument first, then rewrite open decision section for Option B, T9-T13, S14) ## Not yet touched (later sections; PARKED until business definition done) - Section 2/5: Option A/B open-decision section needs rewriting to reflect B chosen. "Telling the member" thread section now relevant to B (DM vs ephemeral vs private thread: still undecided). From 06d67040a8d5b3c112921545d98f0d49a94aa05e Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:37:08 +0100 Subject: [PATCH 13/16] chore: add that the 7 day gives users also enough time to get a read on the server and its community --- docs/adr/0001-onboarding-without-guild-members-intent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.md b/docs/adr/0001-onboarding-without-guild-members-intent.md index 7781b80..8ee55e4 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.md @@ -9,7 +9,7 @@ Regulars have said they are tired of dealing with low-quality content from new j Onboarding exists to serve these goals, in this order of weight: 1. **Protect regulars' attention.** Keep newcomers out of general chat and the rest of the server until they have had time to settle in. Newcomers cannot create showcases, because that goes through a bot button in a channel they cannot reach. They can comment on showcases and take part in the help forum from the start. -2. **Server comprehension.** Newcomers should have seen the rules and learned how to ask a good question before they reach the whole server. +2. **Server comprehension.** The 7-day wait gives newcomers time to read the rules and see how good questions are asked before they reach the whole server. Nothing checks that they did. 3. **Spam and raids.** Fresh accounts have limited reach while trust is unknown. 4. **Drive-by accounts.** People who join, post once and leave should not cost moderators time. 5. **Limit hijacked dormant accounts.** An account that has shown no activity for months can later be compromised and used to post scam messages. A member who has not finished Onboarding and has no activity for 3 months loses the ability to write. Activity means writing a message anywhere, creating a forum post, or joining a voice channel. Finished members are never made dormant, so this goal does not cover them. From 463aaef190ba896f8521b44423df05186ea0835a Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:39:33 +0100 Subject: [PATCH 14/16] chore: rewrite ADR 0001 scratchpad as a resume point Consolidate decisions with reasoning, mark business half finalized and technical sections 2-5 stale and parked, order the parked technical items (intent argument first), and note main doc is uncommitted and unformatted. Co-Authored-By: Claude Sonnet 5.5 --- ...ing-without-guild-members-intent.claude.md | 157 ++++++------------ 1 file changed, 48 insertions(+), 109 deletions(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index fa0c02f..5d54626 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -1,111 +1,50 @@ # Scratchpad: ADR 0001 (onboarding without GuildMembers intent) -Main doc: `0001-onboarding-without-guild-members-intent.md`. Front matter (status) removed from main doc at the human's direction: the ADR carries no status at all. - -## Session 1 state - -Harsh review done (in chat). Nothing in the main doc edited yet. All findings below are OPEN until the human decides. - -## Open findings (need human decisions) - -### Blockers -- B1 Intent rationale self-contradicts: bot already depends on MessageContent (privileged, `src/index.ts:17`); the 400-char mechanic needs it. "Approval could be refused/revoked" applies equally. Also no evidence anyone applied for GuildMembers and was refused. 10,000-user claim unverified. Q: has GuildMembers been applied for? What is the guild/bot size? What is the real reason to avoid the intent? -- B2 Goal 1 (protect regulars) is defeated: start role (granted at join by Discord onboarding) opens help forum + showcase, the exact channels that caused the problem. Q: move those behind finished role, or reword goal 1? -- B3 RESOLVED: status/front matter removed entirely (human: "it should have no status at all"). Section 5 open items still stand on their own. -- B4 Contradictions: line 22/34 say dormant can read; journey table line 45 says dormant loses access to everything except message mods + dormant channel. Table says "start role stays"; Option A removes start role. Rectification text and eval-order step 5 silent on start role. - -### Logic bugs -- (Q3 decided: dormancy now activity-based, see Decisions; L6 largely addressed except finished-member scope and legacy non-posters) -- L5 Messages posted while dormant (dormant channel, Option B) are after the old anchor so get counted; rectification does not delete rows, contradicting "count is already empty". -- L6 Dormancy rule (3 months after anchor w/o finished) does not match goal 5 (never-taken-part accounts). Hits slow active members and active lounge chatters who skipped Discord onboarding (cannot finish without start role). Never touches finished members (real hijack targets). Legacy non-posters invisible at launch. -- L7 Leave grace loophole: kick + rejoin within 7 days keeps progress; time away counts toward the 7 days. -- L8 Launch date listed as both constant (line 145) and env var (line 146). - -### Technical gaps -- T9 No `Partials` in `src/index.ts`; discord.js will not emit messageDelete/messageUpdate for uncached messages, and messageDeleteBulk drops uncached ones. ADR relies on "ID-only delete event". -- T10 Deletes/edits while bot offline are lost; counts drift forever; startup catch-up only replays join messages. -- T11 No estimate of tracked-member count N; daily job does N fetches; dormant rows never expire. -- T12 Read-only needs more than Send Messages deny (threads, forum posts, reactions, voice, attachments); new channels do not inherit. -- T13 Concurrency argument against JSON column applied selectively; evaluate() is also read-modify-write and step 5 deletes rows while inserts may race. - -### Structure -- S14 ADR contains a full spec (journey table, rules, schema, eval order, env vars). Q: split spec out of the ADR into a separate doc? -- S15 Option B is a strawman (defeats goal 5 by its own text); recommend closing on Option A. "Telling the member" private-thread section is scope creep; candidate for follow-up issue. - -### Minor -- Who accepted dormancy gap (line 74) and under what conditions it becomes unacceptable? -- No stated path for "Everyone" members to get start role. -- "a daily job was the requirement": cite source. - -## Proposed section order (not agreed yet) -1. Status + Problem/goals (B2, B3, L6 goal 5 wording) -2. Decision / why not the intent (B1) -3. Journey table + rules (B4, L5, L7, L8) -4. Technical design (T9-T13) -5. Open decision: dormant read-only (S15, T12) - -## Decisions made -- ADR has no status and no YAML front matter. Done in main doc. - -- Q2 (goal 1 vs start role channels), human's facts: new joiners with the start role can COMMENT on showcases in the showcase forum but cannot CREATE showcases. Creation goes through the bot via a button in showcase-guidelines, which the start role cannot reach. Start role can "participate" in the help forum. So the journey table line "write in ... showcase" is inaccurate; must say comment-only. Reasoning: showcase spam is already blocked structurally; the remaining question is help forum posting. -- Q3 (goal 5): dormancy becomes ACTIVITY-based. Store `lastActivity` datetime in DB. Activity = wrote a message anywhere, created a forum post, joined and talked in voice. Each updates lastActivity. Dormant after 3 months with no activity. Replaces "3 months after restart anchor without finished". Reasoning: dormancy should target accounts with no sign of life, not slow members. - -## Implications of Q3 to resolve (asked via AskUserQuestion) -- Does activity dormancy apply to finished members too? Today: finished => row deleted, permanent. Covering finished means keeping a row (lastActivity) for every finished member, which reverses "row deleted on finish" and affects the leave/rejoin design and the no-intent tracking (finished members only seen when they post/join voice). -- Voice: bot cannot detect "talking", only joining (VoiceStateUpdate, GuildVoiceStates already enabled). -- Write volume: updating lastActivity on every message; probably throttle (only write if older than N hours). -- Initial lastActivity for a new row = join date; rectification sets lastActivity = now. -- Interaction with message-count bug L5: dormant-period messages are activity AND get counted; still needs fixing. -- Eval order step 5 and journey table dormant row need rewording once decided. - -## Decisions from AskUserQuestion round (session 1) -- Help forum: start role can CREATE and reply. So goal 1 cannot claim to stop low-effort help requests; draft rewords it and the "do not do" paragraph says so. Flag to human: this means the original trigger (low-effort help requests) is NOT addressed by this ADR. Ask whether that is intended. -- Dormancy scope: ONLY unfinished members. Finished stay permanent and row is still deleted on finish. Consequence: hijack goal covers only never-finished accounts (stated in goal 5 draft). Human chose this over recommended "everyone". -- Voice activity = joining a voice channel (VoiceStateUpdate). -- lastActivity writes throttled (only update when stored value older than an interval, e.g. 1 day). Exact interval to settle in technical design. - -## FINALIZED by human: Problem and goals (goals 1 and 5, gates paragraph) -- Human on the trigger gap: "low effort or low quality messages should not pollute the main area where people hang out, notably general chat. this is why all new comers only have access to the lounge." Intended scope of goal 1 = protect GENERAL CHAT. Trigger paragraph left as is. -- Human on stages: "people without any roles only have access to lounge, then the start role gives access to more channels, and after finishing the onboarding they gain access to the full server." So the journey table channel list is right; just showcase is comment-only. - -## Decisions (journey section round) -- Dormant = Option B (delete and notify). Human chose it over my recommendation A. Known cost, recorded in ADR already: message is visible briefly, weakens goal 5, bot needs Manage Messages and must handle every message from dormant members. Option A text and S15/T12 now partly moot; Option A can be kept in "considered options" instead of "open decision". Start role stays on dormancy (matches table). -- Messages from dormant members are NOT counted (skip recording while dormant role held). Resolves L5. -- Time away does NOT count on rejoin within grace: anchor AND lastActivity shift forward by away time. Human chose this. Away time measured from when the daily job noticed the leave (imprecise by up to a day, consistent with grace period). Resolves L7. Needs stored state: Onboarding row already has "left noticed" time; shift on rejoin. - -## Drafted in main doc (awaiting human review): Journey table and rules -- Start-role row: showcase comment-only, help forum create+reply. -- Dormant row: 3 months without activity; keeps channel access, messages deleted + notified. -- Rules: activity definition (message anywhere incl. forum post, voice join; throttled; initial = join date), dormancy runs from lastActivity, finished never dormant, rectification sets lastActivity=now and dormant messages not counted, rejoin shifts anchor+lastActivity. -- Formatting NOT run: `pnpm exec oxfmt` fails on Node v20.17 (needs newer; .nvmrc). Table padding is unformatted; run fmt before commit of main doc. - -## SCOPE DECISION (human): business definition first, technical details later -Work only on "Problem and goals" and "1. What Onboarding is" (journey + rules) until the business definition is settled. Do NOT edit sections 2-5 (decision, technical design, considered options, open decision) yet. Technical items below are parked, not dropped. Business items still open are listed under "Business questions open". - -## FINALIZED by human: journey table and rules (as drafted in session 1) - -## Business decisions (latest round) -- Dormant notice: member is told in PRIVATE (private thread leaning) immediately when their message is deleted, with a way to reactivate. Reasoning (human): a user who cannot write somewhere will more likely churn than investigate; informing them keeps them in the server and makes reactivating easy; a bot is less likely to react to such a message. OPEN FOR THE TEAM (not the human alone): private thread (member, bot, mods only) vs reply in the channel where everyone sees it. Written into ADR rules as an open point. -- No start role: nothing, their choice, no nudge. Written into rules. -- Gates: 7 days = friction for drive-bys (goals 3, 4). 400 chars = rough engagement sign (goal 6) AND friction; no quality judgement. Human did NOT pick "7 days: time to read rules", so goal 2 (server comprehension) is backed by no gate; only by Discord onboarding presumably. Check with human. - -## Business definition status: essentially complete -- Goal 2 reworded (human chose): the 7-day wait gives time to read rules and see good questions; nothing checks it. Drafted in main doc, awaiting a final look. -- Legacy rule confirmed as written (start+finished+legacy on first post). - -## Business questions open -- Team decision (not ours): private thread vs in-channel reply for dormant notice. Stays as open point in ADR rules. -- Launch date: constant vs env var (L8), technical; park -- Next: either human reads the whole business half once more and signs off, or we move to technical sections (B1 intent argument first, then rewrite open decision section for Option B, T9-T13, S14) - -## Not yet touched (later sections; PARKED until business definition done) -- Section 2/5: Option A/B open-decision section needs rewriting to reflect B chosen. "Telling the member" thread section now relevant to B (DM vs ephemeral vs private thread: still undecided). -- Section 3: Data (add lastActivity column on Onboarding), evaluation order step 5 (dormancy by lastActivity, not anchor), join-message entry point ("changes nothing else" now shifts anchor/lastActivity), message entry point (skip recording when dormant, update lastActivity incl. voice and forum posts), new VoiceStateUpdate entry point, Option B handler (delete messages from dormant members, Manage Messages permission), daily job wording. -- Verify: forum post starter message arrives as MessageCreate; VoiceStateUpdate available. -- L8 launch date in both constants and env vars: still open, decide which. -- B1 (intent argument), T9-T13, S14 (split spec) still open. - -## Still waiting on human -- Review of drafted Journey table and rules -- Launch date: constant or env var (L8) -- Q4: whether to split spec out of ADR (deferred; human chose to edit in place first) +Main doc: `0001-onboarding-without-guild-members-intent.md` (human controls it; Claude never commits it). +Workflow: iterative authoring skill. Ask, don't guess. Prefer AskUserQuestion (human asked for this). Commit only this scratchpad, with descriptive messages. + +## Where we are (end of session 1) + +Business half is DRAFTED in the main doc and the human finalized it, except one last read-through they have not explicitly done: +- "Problem and goals" (goals 1, 2, 5 and the gates paragraph reworded) and "1. What Onboarding is" (journey table + rules). +- Sections 2-5 (decision, technical design, considered options, to-verify/open decision) are UNTOUCHED and now STALE: they still describe anchor-based dormancy, Option A/B as undecided, no lastActivity. +- The main doc has UNCOMMITTED edits (status front matter removed, business half). Human decides when to commit it. `pnpm exec oxfmt` fails on Node v20.17 (needs newer, see .nvmrc), so the widened table rows are unformatted; run fmt on a supported Node before committing. +- Note: AGENTS.md and docs/agents/ are also uncommitted in the working tree and are not ours. + +## Decisions made (business, reasoning kept for context) + +- No status/front matter in the ADR. +- Goal 1 = protect GENERAL CHAT. Human: low-effort or low-quality messages should not pollute the main area where people hang out; newcomers (no roles) only have the lounge. Start role adds help forum (create+reply), ai, gaming, real life, frontend, design, backend, and showcase COMMENTS only. Showcase creation goes through a bot button in a channel the start role cannot reach. Finished = full server. +- Dormancy is ACTIVITY-based: 3 months without activity, unfinished members only (finished are permanent and never dormant; human chose this over covering everyone, so goal 5 does not cover finished accounts). Activity = message anywhere (forum post counts) or joining voice (bot cannot detect talking). lastActivity stored, writes throttled, starts at join date, rectification sets it to now. +- Dormant behaviour = Option B (delete and notify), chosen over my recommendation of Option A (permission deny). Known cost: message briefly visible, weakens goal 5, bot needs Manage Messages. Start role stays on dormancy. +- Messages from members holding the dormant role are not counted toward the 400. +- Rejoin within the 7-day grace: time away does NOT count; anchor and lastActivity shift forward by away time (measured from when the daily job noticed the leave). +- Dormant notice: member told privately and immediately, with a way to reactivate. Reasoning (human): a user who cannot write somewhere churns rather than investigates; this keeps them in the server and makes reactivating easy. +- No start role (skipped Discord onboarding): stays lounge-only, no nudge. +- Gates: 7 days = friction for drive-bys. 400 chars = rough engagement sign and friction; no quality judgement. Goal 2 reworded: the 7 days give time to read, nothing checks it. +- Legacy rule confirmed: start + finished + legacy role on first post. + +## Open items + +Business, for the TEAM to decide (not the human alone): dormant notice in a private thread (member, bot, mods only) vs a reply in the channel itself. Written into the ADR rules as an open point. + +Business, optional: human reads the business half once more and signs off. + +Technical (PARKED by human: "business definition first, technical details later"). Suggested order when resumed: +1. B1 Intent rationale. The bot already depends on MessageContent (privileged, `src/index.ts:17`) and the 400-char mechanic needs it, so "approval could be refused/revoked" applies to it equally. No evidence anyone applied for GuildMembers and was refused. The "10,000 users" claim is unverified. Ask: has GuildMembers been applied for, how big is the server, what is the real reason to avoid it? Revisit "Why not the intent" and "Considered options". +2. Rewrite the "open decision: making dormant members read-only" section: Option B chosen; keep A in Considered options. "Telling the member" (DM vs ephemeral vs private thread) ties to the team decision above. +3. Update section 3 for the new business rules: Data (lastActivity column; maybe left-time already exists), evaluation order step 5 (dormancy by lastActivity, skip recording while dormant), join-message entry point ("changes nothing else" now shifts anchor + lastActivity), message entry point (update lastActivity, throttle), new VoiceStateUpdate entry point, Option B handler (delete dormant members' messages, Manage Messages, exclude the dormant channel), daily job wording, entry-point step 3 legacy check. +4. Technical gaps from the review: + - T9 No `Partials` in `src/index.ts`: discord.js does not emit messageDelete/messageUpdate for uncached messages, and messageDeleteBulk drops uncached ones. The ADR relies on an ID-only delete event. + - T10 Deletes/edits while the bot is offline are lost, counts drift forever; startup catch-up replays only join messages. + - T11 No estimate of tracked-member count N; daily job does N fetches; dormant rows never expire. + - T13 Concurrency argument against the JSON column is applied selectively; evaluate() is also read-modify-write, and step 5 deletes rows while inserts may race. + - T12 (mostly moot under Option B) read-only would need more than Send Messages deny. +5. L8 Launch date is listed as both a config constant and an env var. Decide which. +6. S14 Split the spec (journey, rules, schema, eval order, env vars) out of the ADR? Human deferred; edit in place first. +7. Minor: who accepted the dormancy best-effort gap (invisible members) and when does it become unacceptable; cite the source of "a daily job was the requirement". +8. Verify before building: forum post starter message arrives as MessageCreate; VoiceStateUpdate available (GuildVoiceStates already on); join messages visible in the system channel; single-member fetch works without the intent. + +## Next session: how to start + +Read the main doc and this file, then ask (AskUserQuestion) which to do: (a) business sign-off read-through, or (b) start the technical sections at item 1 (B1). Do not edit sections 2-5 without confirming scope. From a5853b62e0dc22950ecfec65bbe5eb8aebb86e02 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:39:53 +0100 Subject: [PATCH 15/16] chore: note human already committed the ADR edits; re-read before resuming Main doc business-half edits landed in 17e95a4 and 06d6704 (possibly with human tweaks), so the next session must treat the main doc as source of truth. Co-Authored-By: Claude Sonnet 5.5 --- docs/adr/0001-onboarding-without-guild-members-intent.claude.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 5d54626..0b301f1 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -8,7 +8,7 @@ Workflow: iterative authoring skill. Ask, don't guess. Prefer AskUserQuestion (h Business half is DRAFTED in the main doc and the human finalized it, except one last read-through they have not explicitly done: - "Problem and goals" (goals 1, 2, 5 and the gates paragraph reworded) and "1. What Onboarding is" (journey table + rules). - Sections 2-5 (decision, technical design, considered options, to-verify/open decision) are UNTOUCHED and now STALE: they still describe anchor-based dormancy, Option A/B as undecided, no lastActivity. -- The main doc has UNCOMMITTED edits (status front matter removed, business half). Human decides when to commit it. `pnpm exec oxfmt` fails on Node v20.17 (needs newer, see .nvmrc), so the widened table rows are unformatted; run fmt on a supported Node before committing. +- The human has already committed the main doc edits themselves (17e95a4 "clarify goal dormancy", 06d6704 "7 day gives users also enough time to get a read on the server and its community"). The working tree is clean for that file. They may have tweaked wording, so RE-READ the main doc at the start of the next session and treat it as the source of truth over this file. `pnpm exec oxfmt` fails on Node v20.17 (needs newer, see .nvmrc), so the widened table rows may still be unformatted; check `pnpm fmt:check` on a supported Node. - Note: AGENTS.md and docs/agents/ are also uncommitted in the working tree and are not ours. ## Decisions made (business, reasoning kept for context) From 68be35ea198d73a6198e4063b271a52c477b1887 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 2 Nov 2026 15:47:30 +0100 Subject: [PATCH 16/16] chore: queue two new suggestions for next session Capture S1 (block media attachments until Onboarding finished) and S2 (replace Discord rules quiz with writing the rules in a channel) with impact notes and questions to ask; S2 likely reshapes the journey so do it before the technical sections. Co-Authored-By: Claude Sonnet 5.5 --- ...nboarding-without-guild-members-intent.claude.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md index 0b301f1..4c2b5f9 100644 --- a/docs/adr/0001-onboarding-without-guild-members-intent.claude.md +++ b/docs/adr/0001-onboarding-without-guild-members-intent.claude.md @@ -45,6 +45,17 @@ Technical (PARKED by human: "business definition first, technical details later" 7. Minor: who accepted the dormancy best-effort gap (invisible members) and when does it become unacceptable; cite the source of "a daily job was the requirement". 8. Verify before building: forum post starter message arrives as MessageCreate; VoiceStateUpdate available (GuildVoiceStates already on); join messages visible in the system channel; single-member fetch works without the intent. +## New suggestions from the human (end of session 1), NOT yet discussed or scoped + +S1. Prevent media attachments while Onboarding is not finished. +- My notes (unconfirmed): could be a Discord permission (deny Attach Files / Embed Links on the unfinished roles, set by moderators, which fits the rule "the bot only moves roles") or the bot deleting such messages (Manage Messages, briefly visible, like Option B). Journey rules currently say "Attachments add nothing" to the count, so wording changes too. +- Questions to ask: which stage (Everyone, start role, 7-day/messages milestone holders, dormant)? Attachments only, or also embeds/links? All channels or only some? Which goal does it serve (spam/raids, goal 3, and general-chat quality, goal 1)? Does a finished member keep full access? Does dormant need it? + +S2. Replace the Discord onboarding rules quiz (rules shown with 4 answers, 3 wrong and 1 right) with the newcomer writing the rules out in their own words in a channel. +- My notes (unconfirmed): this is a bigger change than it looks. Today Discord onboarding grants the start role and is the only gate for goal 2. Writing in a channel means someone or something must grant the start role afterwards, so the bot (or a moderator) would take over granting it. That would also remove a technical cost in the ADR ("roles granted outside the bot are noticed late"). It touches the "Everyone" stage, "Discord onboarding" in the glossary, goal 2 (the "nothing checks it" wording could become false), the "No start role" rule and the technical sections. +- Questions to ask: what exactly do they write (rules in own words, an introduction, both)? Who validates it: moderators, the bot (length/keywords), or nobody? Which channel, and can the Everyone stage write there (it is currently lounge-only)? Does Discord onboarding still exist for picking the Developer/Designer role? What is the grant mechanism: moderator button/command or automatic? Is spam in that channel a risk (goal 3)? +- Do this as its own business-level discussion BEFORE the technical sections, because it changes the journey. + ## Next session: how to start -Read the main doc and this file, then ask (AskUserQuestion) which to do: (a) business sign-off read-through, or (b) start the technical sections at item 1 (B1). Do not edit sections 2-5 without confirming scope. +Read the main doc and this file, then ask (AskUserQuestion) which to do: (a) business sign-off read-through, (b) the two new suggestions S1 and S2 (business level, S2 may reshape the journey, so probably first), or (c) start the technical sections at item 1 (B1). Do not edit sections 2-5 without confirming scope.