Discord is not connected yet The posts below are sample, shaped exactly like a real read. DISCORD_BOT_TOKEN is not set and no channel is on the allowlist, so there is nothing to mirror. The wiring is at the bottom of this page and it is four moving parts.

Channels being read

ChannelVisible toState
#announcementsEveryone on the rosterAllowlisted
#ops-updatesEveryone on the rosterAllowlisted
#peoplePeople and Client Success, Finance, DirectorsAllowlisted
#generalNobodyNot read
#clients-privateNobodyNot read
Direct messagesNobody, everOut of scope

A channel is read only if its ID is on the allowlist. The bot being able to see a channel is not the same as the portal fetching it, and the second one is what governs.

Try this

Switch role to Business Development.

The #people post about the timesheet cut-off disappears from the feed. Channel visibility is scoped by role in exactly the same table that scopes the modules, so a payroll conversation does not leak into a sales screen just because both people are in the same server.

Direction of travel

Discord stays the place people talk.

Nobody posts here. There is no reply box and there will not be one. This screen exists so an announcement is still findable on the Monday after it scrolled past, not to move the conversation into a second app that half the team forgets to open.

Latest announcements

Last sync: never · token not set
  • Pinned
    PP Patrick Panuncillon #announcements 6 Aug

    Payroll goes into parallel run this period. Angela prepares as normal in the spreadsheet, the portal prepares the same period beside it, and Patmae reconciles the two to the peso.

    Nobody is paid from the portal until those two numbers match on a full period. If they disagree by one centavo we find out why before anything is released.

  • LA Lois Alberio #announcements Today, 08:52

    Sunrise Dental deck is out for internal review. Two things I want eyes on:

    • The Tagum City query set. I used profession-led for all four and I think one should be category-led.
    • Whether the Grow tier note reads too hard for a first conversation.
  • AS Angela Sabejon #people Yesterday, 16:20

    Reminder on the cut-off. Timesheets for 1 to 15 August close on the 16th at noon. Anything filed after that lands in the next period.

    I would rather not backdate, and once a run is locked I cannot anyway.

  • PC Patmae Cubos #announcements 5 Aug

    MACE proposal is signed. Kick-off call is Monday at 10:00.

    Patrick, the SMS rate stays pinned at ₱2.00 per segment in every document that leaves this office. If a quote says anything else, it is old.

  • RC Richie Cubos #ops-updates 4 Aug

    Two new client logos are in the shared folder. Please use the versions in public/logos and not the ones from the email thread. The email set is the old lockup and it has already gone out on one deck.

  • PP Patrick Panuncillon #ops-updates 3 Aug

    The internal portal demo is on staging. It is presentation only, nothing is stored, and every payroll figure on it is sample data.

    Have a look, tell me what is missing. Do not quote any number from it to anyone.

Last 30 days or 50 posts, whichever is smaller · Discord remains the record, this is a mirror · No reply box, by design

Integration

How Discord gets here, and where it stops.

A Discord webhook only points inward, so it cannot be used to read a channel. Reading needs a bot, and a bot is exactly where a careless setup hands a side project permission to read every conversation in the company. The scope below is deliberately narrow.

The read

  1. A bot with two permissionsView Channel and Read Message History, on the allowlisted channels only. No send, no manage, no member list, no reactions.
  2. Polling over HTTPS, not a gateway socketA Worker cannot hold a websocket open to Discord for hours, and it does not need to. The scheduled Worker calls the REST API every few minutes, which is the reason this fits the stack at all.
  3. Allowlist by channel IDThe token is a Wrangler secret and the allowlist is a table. Adding a channel is a deliberate row, not a side effect of somebody inviting the bot somewhere.
  4. Sanitised before it rendersA safe markdown subset, mentions resolved to display names, attachments linked rather than embedded, everything else escaped. Chat is user input and it is rendering inside an authenticated tool.
  5. Cached, then aged outPosts land in D1 and expire at 30 days. Discord holds the history. Duplicating it here would only create a second thing to secure.

Boundaries

Direct messages
Never reachable. A bot in a server has no path to anyone's DMs, and nothing here asks for one.
Other channels
Not fetched even where the bot can see them. Visibility and retrieval are separate decisions.
Editing
None. The portal cannot post, edit, pin, or delete anything in Discord.
Role scoping
Each channel maps to the same roles table that governs the modules, so a channel can be read by the portal and still be invisible to a role.
Deletions
A post deleted in Discord drops out of the mirror on the next pull. The mirror never outlives the original.
Audit
Every sync writes one line to the audit log. A read that leaves no trace is a read nobody can review.

The reverse direction, later

Posting into Discord needs no bot at all, only a webhook URL. A released payroll run or a delivered Snapshot could announce itself in a channel. That is genuinely useful and it is also Phase 3, because an outbound notification that fires on sample data teaches the team to ignore notifications.

One shared dependency Both this screen and the Priorities screen need the standalone scheduled Worker, because Cloudflare Pages cannot run cron, queue consumers, or a scheduled handler. That Worker was previously sitting in Phase 3 as a payroll rollover job. Two integrations now depend on it, which moves it earlier and is worth naming rather than discovering.
Fourth Ground Consulting · Internal Operations Portal · Presentation-only demo, August 2026
Staging Environment Fourth Ground Consulting