/vetting — Slash Command Reference¶
Commands¶
In workflow order:
/vetting session timeful [link]
/vetting session timeful-refresh [link]
/vetting session info
/vetting session new [date] [time]
/vetting session add [person]
/vetting session remove [person]
/vetting session announce
/vetting session ingest
General Notes¶
Vetting_sessions schema — writable fields relevant to the bot:
vetting_datetime— DateTime (null until/vetting session newis run)vettees— ReferenceList("People") — write target for vetteesvetting_panel— ReferenceList("People") — write target for panel membersno_shows— ReferenceList("People")notes— Texttimeful_url— Text — Timeful poll URL, written bytimefultimeful_respondents— Text (JSON) —{"display_name": people_row_id_or_null}, written bytimeful-refreshschedule_message_id— Text — Discord message ID of the scheduling post, written bytimefulannouncement_message_id— Text — Discord message ID of the session announcement, written byannounce
vettees_person_id is a read-only formula that returns rec.vettees.person_id — it is not a write target. Do not write to it.
Vetting_session_notes and Prevetting_vetting_notes are both accessible to the bot editor account. Notes are kept vague enough that neither table is considered PII-restricted for bot access purposes.
Session lifecycle¶
A Vetting_sessions row is created by /vetting session timeful at the start of planning, with vetting_datetime null. /vetting session new patches vetting_datetime onto that row once a time is confirmed. If no unscheduled row exists when new runs (backfill), a new row is created directly.
/vetting session timeful¶
Signature¶
/vetting session timeful [link]
Access¶
Admin (unvetted server)
Purpose¶
Starts session planning: creates a Vetting_sessions row, posts a vetting availability poll announcement to the announce channel using templates/vetting/schedule.md, and stores the Timeful URL and scheduling post message ID on the row. The operator creates the Timeful poll manually first, then passes the link to this command. due_date is computed as 25 hours before midnight of the poll's first date (e.g. first date 5/27 → due 5/25 11:00 PM). Once responses are in and a time is chosen, use /vetting session new to set the date.
Parameters¶
link— URL of the manually-created availability poll
Timeful API¶
Timeful exposes a public unauthenticated REST endpoint — no API key, no headless browser needed:
GET https://timeful.app/api/events/{short_id}
Returns: poll name, date range (dates[]), respondent names and count (responses{}), and a scheduledEvent field (non-null if the organizer has locked in a time). The short ID is the last path segment of the poll URL (e.g. fAfBA from https://timeful.app/e/fAfBA). See scratch/timeful_test.py for a working example.
Flow¶
- Call Timeful API with the provided link to confirm it resolves and pull the date range
- Create a
Vetting_sessionsrow withtimeful_urlset - Post announcement to announce channel (non-ephemeral) using
schedule.mdwith{{date_range}},{{timeful_url}},{{due_date}},{{unvetted_role}}substituted - PATCH
Vetting_sessions.schedule_message_idwith the posted message ID
/vetting session timeful-refresh¶
Signature¶
/vetting session timeful-refresh [link]
Access¶
Admin (unvetted server)
Purpose¶
Fetches current responses from a Timeful poll and shows a linking UI. Respondents already in Vetting_sessions.timeful_respondents are shown as ✅ with no action needed. New respondents get a UserSelect to link to a guild member. On confirm, writes $People.last_schej_RSVP for all linked respondents and persists the updated name→ID map. link is optional — defaults to the URL stored on the current upcoming session.
Parameters¶
link— URL of the Timeful poll (optional; defaults toVetting_sessions.timeful_url)
last_schej_RSVP field¶
$People.last_schej_RSVP stores the date the member last submitted Timeful availability. The value written is the first date in the poll's dates[] array (i.e. the start of the date range, not the submission date).
Flow¶
- Call
GET https://timeful.app/api/events/{short_id}— extract respondent names anddates[0]as the reference date - Load
Vetting_sessions.timeful_respondents(stored name→ID map) for the current upcoming session - Classify each respondent: already in stored map with a non-null ID → ✅ already linked; otherwise → needs manual link
- Post ephemeral response with already-linked respondents (sorted by days waiting) and a
UserSelectfor each unlinked name (up to 4):
📋 Timeful responses (8 respondent(s))
✅ .morgan.le.fae → Display Name (@username) (104d)
✅ shakethespear → Display Name (@username) (75d)
⚠️ Alexmkzero — unlinked
⚠️ K8 — unlinked
...
- On confirm: PATCH
$People.last_schej_RSVPfor all linked respondents (already-linked + newly selected); updatetimeful_respondentswith new links
Linking UI¶
For each unlinked respondent (up to 4), show a Discord UserSelect labeled with the Timeful display name. Operator picks the guild member; bot resolves to $People via snowflake. Dismiss button makes no changes.
Notes¶
last_schej_RSVPis a Grist Date column. Writedates[0]parsed to adatetime.date, converted via_to_unix()(noon UTC).- Can be run multiple times; already-linked names are never re-asked. New respondents who joined the poll since the last run will appear as unlinked.
/vetting session new¶
Signature¶
/vetting session new [date] [time]
Access¶
Admin (unvetted server)
Purpose¶
Sets the date and time for the upcoming session. If an unscheduled Vetting_sessions row exists (created by timeful), patches its vetting_datetime. If no unscheduled row exists (backfill case), creates a new row.
Parameters¶
date— parsed flexibly (e.g.6/14,June 14,2025-06-14)time— 12h or 24h format (e.g.6pm,18:00), combined with date intovetting_datetime
Response¶
Ephemeral:
✅ Vetting session scheduled — Sat Jun 14 2025, 6:00 PM
Use /vetting session add to add vettees.
/vetting session add¶
Signature¶
/vetting session add [person]
Access¶
Admin (unvetted server)
Purpose¶
Adds a vettee to the upcoming vetting session and creates a row for them in Vetting_session_notes so note-takers can record session notes without Grist access. If a session announcement has been posted, edits it to reflect the updated vettee list. The person param autocompletes from all guild members.
"Upcoming session" definition¶
The nearest Vetting_sessions row with a future vetting_datetime, or the unscheduled row if no scheduled session exists yet.
Flow¶
- Resolve
personparam (Discord snowflake from autocomplete) →$Peoplerecord viaresolve_member - Find upcoming session
- PATCH
Vetting_sessions.vetteesto append the person (ReferenceList) - Create a row in
Vetting_session_noteswithvalidated_identityset to the person - Edit the announcement post if
announcement_message_idis set on the session - Reply ephemeral confirmation
Response¶
✅ alice added to vetting session (Jun 14 2025, 6:00 PM)
/vetting session remove¶
Signature¶
/vetting session remove [person]
Access¶
Admin (unvetted server)
Purpose¶
Removes a vettee from the upcoming session. The person param autocompletes from all guild members; the command validates the selected person is in the session. Edits the announcement post if one has been posted.
Response¶
✅ alice removed from vetting session.
/vetting session announce¶
Signature¶
/vetting session announce
Access¶
Admin (unvetted server)
Purpose¶
Posts the session announcement to the announce channel using templates/vetting/session.md, with {{session_datetime}}, {{vettees_list}}, and {{attendee_role}} substituted. Stores the message ID in Vetting_sessions.announcement_message_id so subsequent add/remove calls can edit it in place.
Response¶
Rendered templates/vetting/session.md posted to announce channel; ephemeral ✅ Announcement posted. to caller.
/vetting session ingest¶
Signature¶
/vetting session ingest
Access¶
Admin (unvetted server)
Purpose¶
After a session concludes, moves notes from Vetting_session_notes into Prevetting_vetting_notes, linked to the correct vettees.
Why two tables¶
Vetting_session_notes is a public Grist table — note-takers who don't have a Grist account can still enter notes there during the session. Ingest links them to the right vettees in Prevetting_vetting_notes after the fact.
Flow¶
- Find the most recent past session (nearest
Vetting_sessionsrow withvetting_datetimein the past) - For each person in
vettees: fetch theirVetting_session_notesrow (matched byvalidated_identity) and their existingPrevetting_vetting_notesrow (matched byvalidated_username) - PATCH
Prevetting_vetting_noteswith the notes content using the field mapping below - Reply with a summary
Field mapping¶
Vetting_session_notes |
Prevetting_vetting_notes |
|---|---|
want_to_go_by |
What_is_the_name_you_would_like_to_go_by_during_the_vetting_session_handle_alias_name_all_ok_ |
pronouns |
What_are_your_pronouns_ |
what_inspired |
What_inspired_to_join |
primary_goals |
Training_goals |
value_to_org |
Value_to_org |
activism_ |
Activism_ |
support_system |
Support_system |
socialist_org |
Socialist_org |
civil_nonsectarian |
Civil_nonsectarian |
vetting_notes |
Vetting_Notes |
prevetting |
Prevetting |
ideology |
Ideology |
persec_opsec |
Persec_opsec |
validated_identity |
validated_username |
Response¶
✅ Vetting session ingested (Jun 14 2025, 6:00 PM)
Notes moved for: alice, bob, carol
Notes¶
Vetting_session_noteshas noReference("Vetting_sessions")— ingest finds notes by matchingvalidated_identityagainst the session'svetteeslist.- No
ingestedmarker is written toVetting_sessions— the operator is responsible for running ingest only once per session. - Idempotency: running ingest twice will overwrite
Prevetting_vetting_notesfields with the same values, which is harmless.
/vetting session info¶
Signature¶
/vetting session info
Access¶
Admin (unvetted server)
Purpose¶
Shows a summary of the most recently created vetting session. Fetches fresh Timeful data for the date range, reads the stored timeful_respondents map for match display, and shows vettees and post links.
Flow¶
- Find the most recently created
Vetting_sessionsrow (by row ID, not datetime) - Display:
vetting_datetime(or "not yet scheduled") — flag with ⚠️ and help text if in the past- Timeful poll link
- Links to scheduling post (
schedule_message_id) and announcement post (announcement_message_id) - If
timeful_urlis set, call the Timeful API for the current date range; readtimeful_respondentsfor match state: - Matched respondents: ✅ name → display (@username) (Nd), sorted by days waiting desc
- Unmatched: ⚠️ name — unmatched/unlinked
- Vettees list with Discord mentions
Response¶
Ephemeral summary to caller.
Vetting Notes — Approach¶
Vetting_session_notes acts as a staging table during the session (public, accessible without a Grist account). /vetting session ingest links those notes to the correct vettees in Prevetting_vetting_notes after the fact. The bot editor account has access to both tables.
Future work¶
- No-show logging —
/vetting session noshow [@member]to PATCHVetting_sessions.no_showsand optionally trigger aStatus_actionsrow. - Vetting completion —
/vetting pass [@member]to add aStatus_actionsrow withstatus="vetted". Thepassed_vetting_buttonaction in$Peopleshows exactly what that write looks like.