Skip to content

v1.1.0

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 13 Aug 08:35
· 61 commits to production since this release
167666a

Added

  • Infernoplex the standalone Rust Discord server-tracking bot has been
    ported into Popplio's own binary as a new infernoplex/ package, the same
    treatment Arcadia got earlier. main.go now starts it right alongside
    Arcadia (infernoplex.Start(state.Context), stopped with the same 30s
    grace period on shutdown) instead of it running as a separate service.
    The port covers everything the Rust bot did: a guided multi-step server
    setup wizard (infernoplex/bot/setup.go), invite creation/resolution
    (infernoplex/invite), a server-info push command gated on "Edit
    Servers" (cmdUpdate), a vote leaderboard (cmdLeaderboard), a
    bot-stats command (cmdStats version/Go version/git commit/env, mirrors
    Arcadia's /info), and background tasks for server/emoji/sticker sync and
    team-member cleanup (infernoplex/tasks). It also runs its own small
    internal HTTP API, "Sorbet" (infernoplex/sorbet), structured the same
    way as Arcadia's panel dispatch. The standalone Rust Infernoplex service
    is superseded by this and should be decommissioned.
    Config shape change (update config.yaml before deploying): a new
    infernoplex: block with client_id/client_secret plus per-environment
    prefix/server_port/token (same Differs[T] staging/prod/beta/dev
    pattern used elsewhere) — see config.yaml.sample.
  • Infernoplex's leaderboard command now replies with a "No Votes Yet" embed
    instead of an empty/broken one when a server has zero votes.
  • Self-hosted proof-of-work vote captcha (popplio/captcha), replacing the
    dead HCaptchaInfo scaffolding in types/vote.go (which was never wired
    to anything) with something actually enforced. GET /votes/captcha/challenge issues a signed, stateless hashcash-style
    challenge (find a nonce so sha256(salt+":"+nonce) has N leading zero
    bits); PUT .../votes now requires a solved challenge in the request body
    for bot/server votes unless the entity has opted out via the existing
    captcha_opt_out setting. Challenges are HMAC-signed with the new
    captcha.hmac_secret config value so they can't be forged, and each
    solved challenge is single-use (consumed in Redis on first successful
    verification) so a solve can't be replayed across multiple votes. No
    third-party captcha provider involved — the whole protocol lives in
    popplio/captcha.
    Config shape change (update config.yaml before deploying): a new
    captcha: block with a per-environment hmac_secret (same Differs[T]
    pattern used elsewhere) — see config.yaml.sample. Generate one with e.g.
    openssl rand -hex 32; rotating it invalidates all outstanding
    challenges.

Changed

  • Rebranded "Infinity List" → "Omniplex" across every remaining user-facing
    string that still had the old name: the MFA issuer shown in a staff
    member's authenticator app on re-enrollment (arcadia/panel/mfa.go), the
    staff bot's /analytics embed title (and its frozen conformance string),
    and the fallback SEO description on GET .../teams/{id}/seo when a team
    has no custom short description ("View the team X on Omniplex"). A few
    doc comments got the same treatment with no functional effect.
  • Every reply the staff bot makes is now an embed, including one-liners
    (the "Isabelle" rewrite, #43). A bare content message is indistinguishable
    from a staff member talking, which matters in the staff server where the
    bot's answers and the conversation share a channel. Ctx.Say builds the
    embed itself, so this is a change of container rather than of wording —
    every string frozen in arcadia/conformance is untouched and still
    asserted. Two coloured variants went in alongside it: Ctx.Fail (red) for
    the command guards, the panic handler and the "there was an error" paths,
    and Ctx.Ok (green) for the 16 replies that report something having
    worked, so a refusal is visibly different from an answer without either
    having to say so. The modal driver and the permission editor's ephemeral
    refusals, which answer through the interaction rather than through Ctx,
    build the same shape by hand (modalReply in
    arcadia/bot/interactions.go). TestRepliesAreEmbeds walks the package's
    AST for any MessageCreate that sets Content and fails if one appears.
  • A second pass over the same files, this time pulling out the repetition
    rather than only moving it. In arcadia/rpc: modLogReason builds the
    mod-log embed nine handlers were each building by hand (title,
    description, one Reason field, footer, colour), reasonField covers the
    four multi-field embeds that keep their own shape, and
    guardBot/guardUser replace the ten copies of "reject an over-long
    reason, then check the target exists". certifyAdd went from 47 lines to
    20 this way, and review.go split into claim.go and verdict.go once
    it had. In arcadia/panel: authorize replaces the ten copies of the
    twelve-line checkAuth + resolvedPerms preamble, and ops_core.go
    (608) split into ops_auth, ops_hello, ops_queue, ops_rpc,
    ops_search and ops_proxy. arcadia/tasks/staffresync.go (579) split
    into the resync itself, its reporting and its Discord role mirroring.
    What was deliberately not factored out: the frozen embed and error
    strings stay written out at their call sites, because
    arcadia/conformance finds them by scanning the source for the literal —
    a helper that formatted them would pass its own tests while quietly
    removing that check. For the same reason the SQL stays literal at each
    call site, since arcadia/dbconform PREPAREs every string literal it can
    find against a real database. And the five steps of StaffResync are
    left inline: they share a transaction and a working set that each step
    narrows, so splitting them would make an ordering that is load-bearing
    look optional.
  • The five files that had grown past the point of being navigable are split
    by what they do, with no behaviour change: arcadia/bot/staffroles.go
    (1137) into staffmgmt.go (the role model, the authority rules, the
    lookups), staffroles.go, staffperms.go and staffrender.go;
    arcadia/bot/commands.go (697) into commands.go (the registry and the
    two shared RPC helpers) plus help.go, invites.go, stats.go and
    staffops.go; arcadia/panel/ops_shop.go (861) into one file per shop
    concern (tiers, items, benefits, coupons, whitelist);
    arcadia/panel/ops_staff.go (759) into positions, members and
    disciplinaries, with its two shared existence checks moved to
    ops_query.go where the shop operations that also use them can find
    them; and arcadia/bot/permeditor.go (858) into
    session/apply/render/util. Each new file opens with what it covers and
    what is non-obvious about that area. All of it is code movement verified
    line-for-line against the original; nothing in the repo's directory
    structure changed, and routes/'s one-package-per-endpoint layout is
    left alone since uapi requires it.
  • arcadia/rpc/methods.go (878 lines, every RPC action in one file) is
    split into one file per group of actions, grouped exactly the way
    types.rpcPermissions groups them — so the file an action lives in is
    the same question as which permission gates it: review.go (claim,
    unclaim, approve, deny, unverify), certify.go, transfer.go,
    forceremove.go, premium.go, votes.go, apps.go, plus dispatch.go
    for the method-to-handler switch and audit.go for the
    staff_general_logs write. core.go keeps the Execute pipeline and
    the shared guards, and its package doc now carries the map of where
    things live and the note that every mod-log embed is reproduced verbatim
    from the Rust original (that note used to sit above the dispatcher and
    said "every embed below", which the split would have made a lie). Pure
    code movement: every moved line is byte-identical to what it replaced,
    and arcadia/conformance scans the whole package rather than one file,
    so it pins the embed strings exactly as before. (review.go was later
    split further into claim.go/verdict.go — see the dedup-pass bullet
    above; arcadia/CONFORMANCE.md's file references still say review.go
    in a few spots and need updating to match, see Known Issues below.)
  • The Dev Team staff application no longer requires or mentions Rust
    (description and two questions updated to reflect Go/TypeScript only);
    the QAQC application track was removed entirely. Consistent with Arcadia
    and now Infernoplex both being fully off Rust.

Security

  • Only the prod instance now sets the main Discord bot's gateway presence
    (state.go's OnGuildsReady handler). Staging/beta/dev instances still
    connect and function normally, they just no longer call
    SetPresenceForShard, so a non-prod checkout — misconfigured shared
    token or otherwise — can never overwrite what the public bot's profile
    shows as its "Watching" activity.

Removed

  • Five retired permissions — view_shop, manage_shop,
    manage_bot_whitelist, view_cdn, manage_cdn purged from every
    stored permission array (staff_positions.perms,
    staff_members.perm_overrides, staff_disciplinary_types.perm_limits)
    via a new one-off migration, exp/rewrite/remove_broken_perms.sql
    (needs to be applied manually against the database like other exp/
    scripts).

Known issues found during this pass, not yet fixed

  • Infernoplex's new "No Votes Yet" message has a typo: "Unfortuently, your
    server has no votes at this time."
  • config/config.go's Naevis struct (added alongside Infernoplex as an
    apparent placeholder for a second bot) is dead code it's never
    referenced from the top-level Config struct despite its fields being
    tagged validate:"required", and config.yaml.sample's naevis: section
    was already removed. Safe to delete outright, or finish wiring it up if
    Naevis is still planned.
  • arcadia/CONFORMANCE.md references a arcadia/rpc/review.go in a few
    places (issues #6, #9, #10) that doesn't exist the file is verdict.go.
    Looks like a stale rename from drafting the Isabelle split.