Epic for the 2026-08 security + quality audit of packages/core (mineral v5.1.1).
The audit ran 11 parallel review dimensions over the 516 files of packages/core; every
finding below survived an adversarial refutation pass. Findings are numbered A1-A29
to avoid collision with the H*/M* codes used by the previous audit (#424-#428).
Why this chantier exists
The security posture came out good: the bot token is never logged, IDENTIFY/RESUME
payloads stay out of the logger, inbound gateway traces go through redactSensitiveFields,
HTTPS is enforced at HttpClient construction, CI uses OIDC with SHA-pinned actions, and
all 21 Intent bits plus every Permission position were verified against the Discord spec.
The problem is elsewhere: several public paths are broken 100% of the time in the
published release.
message.delete(), message.pin() and every reaction throw a raw TypeError (any 204).
guild.roles.get/create/update and guild.emojis.fetch/get throw type 'Null' is not a subtype of type 'String'.
Guild.assets.icon, .banner, .splash, settings.permissions, .afkTimeout and
.vanityUrlCode are silently always null.
- Every voice-state deserialization throws.
- Every GIF or LOTTIE sticker throws a
StateError.
Root cause (A0)
All five share one cause: the rawDiscordPayload() fixtures were written to match what the
serializer expects, not the real Discord wire shape. The suite validates the code against
itself — it is green, the analyzer is clean, and the framework does not work.
That is why #459 lands first: it rebuilds the fixtures from real payloads and adds a
round-trip assertion per serializer, turning the suite red so the wave-1 cards turn it green.
Fixing the five call sites without fixing the fixtures brings the whole class straight back.
Branch topology
- Workstream branch
chantier/audit-2026-08, cut from main.
- One feature branch per card, cut from the chantier branch (never from
main).
- Feature branch -> chantier branch: squash.
- Chantier branch ->
main: a single squash PR, merged by the maintainer.
Sequencing
Wave 0 lands first. Waves 1-5 are otherwise parallel, except for these file collisions:
Cards
Wave 0 — test contract (blocks wave 1)
Wave 1 — payload contracts (the shipped-broken paths)
Wave 2 — HTTP layer
Wave 3 — gateway resilience
Wave 4 — consumer-facing correctness
Wave 5 — security & release
Wave 6 — architecture & debt
Follow-up
Audit scope caveats
The verification pass was budget-capped: one refutation lens instead of two, on a smaller
model, applied to Critical/High/Medium only. 0 of 33 findings were refuted — an
abnormal rate. The evidence supports the findings (several were reproduced in standalone
Dart scripts, and env_guard/eterl sources were read in .pub-cache), but treat the
Low cards as probable rather than confirmed until the regression test is red.
Not covered by this audit: the api/ entity layer has one test file for 202 source files;
packages/cache and packages/test were out of scope.
Status — 18 of 23 cards done
The chantier branch was rebased onto main and shipped (#489). main now carries
the payload-contract, HTTP, gateway-resilience, handler-isolation, security and
CI work; all three packages are green on format, analyze and test.
Still open, and none of it fixes a user-visible bug:
Loose ends found during the work and not yet carded: see the follow-up issue
linked below.
Epic for the 2026-08 security + quality audit of
packages/core(mineral v5.1.1).The audit ran 11 parallel review dimensions over the 516 files of
packages/core; everyfinding below survived an adversarial refutation pass. Findings are numbered
A1-A29to avoid collision with the
H*/M*codes used by the previous audit (#424-#428).Why this chantier exists
The security posture came out good: the bot token is never logged, IDENTIFY/RESUME
payloads stay out of the logger, inbound gateway traces go through
redactSensitiveFields,HTTPS is enforced at
HttpClientconstruction, CI uses OIDC with SHA-pinned actions, andall 21
Intentbits plus everyPermissionposition were verified against the Discord spec.The problem is elsewhere: several public paths are broken 100% of the time in the
published release.
message.delete(),message.pin()and every reaction throw a rawTypeError(any 204).guild.roles.get/create/updateandguild.emojis.fetch/getthrowtype 'Null' is not a subtype of type 'String'.Guild.assets.icon,.banner,.splash,settings.permissions,.afkTimeoutand.vanityUrlCodeare silently alwaysnull.StateError.Root cause (A0)
All five share one cause: the
rawDiscordPayload()fixtures were written to match what theserializer expects, not the real Discord wire shape. The suite validates the code against
itself — it is green, the analyzer is clean, and the framework does not work.
That is why #459 lands first: it rebuilds the fixtures from real payloads and adds a
round-trip assertion per serializer, turning the suite red so the wave-1 cards turn it green.
Fixing the five call sites without fixing the fixtures brings the whole class straight back.
Branch topology
chantier/audit-2026-08, cut frommain.main).main: a single squash PR, merged by the maintainer.Sequencing
Wave 0 lands first. Waves 1-5 are otherwise parallel, except for these file collisions:
request_bucket.dart— fix(http): restrict ResilientHttpClient retries to idempotent methods #465 before fix(security): bucket rate limits on a token-free key and add in-flight accounting (A21/A24) #473shard.dart— fix(wss): repair the reconnect and resume paths (A5/A6) #466 before fix(wss): stop unhandled async errors from terminating the host process (A7/A16) #467Cards
Wave 0 — test contract (blocks wave 1)
Wave 1 — payload contracts (the shipped-broken paths)
guild_idbeforenormalizein the role and emoji parts fix(marshaller): inject guild_id before normalize in the role and emoji parts (A13/A14) #460normalizeactually produces fix(marshaller): read guild assets and settings from the shape normalize produces (A15) #461Wave 2 — HTTP layer
Wave 3 — gateway resilience
Wave 4 — consumer-facing correctness
Wave 5 — security & release
EnvPlaceholdersnapshotting the whole process environment fix(security): stop EnvPlaceholder from snapshotting the whole process environment (A1/A2) #472env_guardwholesale from the public barrel refactor(api): stop re-exporting env_guard wholesale from the public barrel (A23) #475Wave 6 — architecture & debt
Event.parameters; link payload types to event identity refactor(events): delete Event.parameters and link payload types to event identity (A19) #476CommandBuildera real interface refactor(commands): give CommandBuilder a real interface (A25) #478Follow-up
Audit scope caveats
The verification pass was budget-capped: one refutation lens instead of two, on a smaller
model, applied to Critical/High/Medium only. 0 of 33 findings were refuted — an
abnormal rate. The evidence supports the findings (several were reproduced in standalone
Dart scripts, and
env_guard/eterlsources were read in.pub-cache), but treat theLowcards as probable rather than confirmed until the regression test is red.Not covered by this audit: the
api/entity layer has one test file for 202 source files;packages/cacheandpackages/testwere out of scope.Status — 18 of 23 cards done
The chantier branch was rebased onto
mainand shipped (#489).mainnow carriesthe payload-contract, HTTP, gateway-resilience, handler-isolation, security and
CI work; all three packages are green on format, analyze and test.
Still open, and none of it fixes a user-visible bug:
Event.parameters, link payload types to event identity. ~2200 linesacross three file families, one already over the repo's 1k threshold. Needs a
decomposition pass before it can be worked in reviewable pieces.
serialize()has twocallers with different contracts, and the cache path bypasses
normalize()entirely. Needs a design pass, not an implementation pass.
Loose ends found during the work and not yet carded: see the follow-up issue
linked below.