Skip to content

Commit 5fb28ad

Browse files
StephenSookclaude
andcommitted
chore(release): v0.5.4 — Wave AD-review CRITICAL/HIGH fixes from agent dispatch
Stephen requested a deep-dive review of v0.5.3. code-reviewer + silent-failure-hunter agents (dispatched in parallel) surfaced 5 real regressions: 3 CRITICAL (substring classifier bug, timed-out word mismatch, apiKey resolve outside try) + 2 HIGH (simulate-rule toast contradiction + missing first-error). 5 atomic fixes landed. Plus LOW #9 — extracted the OpenAI classifier to src/lib/openaiErrors.ts to eliminate the api.ts + forms.ts dupe that already drifted once. Plus LOW #10 — wrapped recordEvent in the triggers.ts config-read-fail recovery paths so a still-ongoing Redis blip can't 500 the recovery. 516 tests pass (+11 classifier regression suite). See CHANGELOG.md [0.5.4] for full breakdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 00f46f8 commit 5fb28ad

2 files changed

Lines changed: 79 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,84 @@ The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.
66

77
## [Unreleased]
88

9-
Forward-looking (post-v0.5.3): see [`ROADMAP.md`](./ROADMAP.md).
9+
Forward-looking (post-v0.5.4): see [`ROADMAP.md`](./ROADMAP.md).
10+
11+
## [0.5.4] — 2026-05-18
12+
13+
Wave AD-review — Stephen requested a deep-dive review of the v0.5.3
14+
ship. Dispatched `pr-review-toolkit:code-reviewer` +
15+
`pr-review-toolkit:silent-failure-hunter` agents in parallel; both
16+
surfaced real regressions in the AD Tier-1 #3 + Phase 4 work that the
17+
local triple-gate didn't catch.
18+
19+
### Fixed — CRITICAL (security / correctness)
20+
21+
- **isTransientOpenaiError `'5'` substring bug**`lower.includes('5')`
22+
matched any error string containing the digit 5. Real exposure:
23+
`'Paste a rule JSON5 in the form field, then submit.'` (the
24+
/explain-rule-submit empty-input error) was being classified as a
25+
transient OpenAI outage → recordFailure → breaker opened on a typo,
26+
punishing the entire install. Tightened to a word-boundary 5xx regex
27+
`(?:^|\D)5\d{2}(?:\D|$)`.
28+
- **isTransientOpenaiError `'timed out'` vs `'timeout'`**
29+
classifier checked `lower.includes('timeout')` (one word) but
30+
`explainEvent.ts:151` emits `'OpenAI request timed out after 30s. Retry.'`
31+
(two words). Real OpenAI 30s timeouts were NOT tripping the breaker,
32+
defeating the entire X37/X43 + AD Tier-1 #3 fix. Added `'timed out'`
33+
alongside `'timeout'`.
34+
- **apiKey resolve outside try in /explain-event + /explain-rule-submit**
35+
`getOpenaiKey` + `settings.get` were called BEFORE the try wrapping
36+
the OpenAI call. A Redis blip or settings throw 500'd the route
37+
with NO `log.error`, NO breaker classification, NO json response.
38+
Wrapped in their own try → 503 + structured log on failure. Does
39+
NOT trip the breaker (Redis/settings being down isn't an OpenAI
40+
outage).
41+
42+
### Fixed — HIGH
43+
44+
- **simulate-rule-submit toast contradiction** — when every sample
45+
failed to normalize, `formatSimulationToast` returned
46+
"No recent posts to simulate against." while the AD Tier-1 #2
47+
suffix said "(N/N skipped — normalize error)". Now returns a
48+
dedicated "Simulation aborted: every sample failed to normalize
49+
(first: ...)" toast with the first error excerpt.
50+
- **simulate-rule-submit skip-counter loses error message** — the
51+
per-post catch only logged + counted, the toast just said
52+
"normalize error" with no actionable info. Now captures
53+
`firstSkipError` and appends "(N/M skipped — first: <60 chars>)"
54+
so the mod sees a real cause.
55+
56+
### Fixed — LOW
57+
58+
- **isTransientOpenaiError extracted to `src/lib/openaiErrors.ts`**
59+
was duplicated byte-for-byte in `api.ts` + `forms.ts` w/ a
60+
"keep in sync" mirror comment. Both copies had the same two
61+
CRITICAL bugs above; the mirror approach already drifted (one
62+
copy had an inline `// 5xx HTTP` comment, the other didn't).
63+
Eliminates the drift class entirely.
64+
- **triggers.ts `recordEvent` unwrapped in config-read-fail recovery**
65+
the catch blocks exist to prevent a Redis blip from 500-ing the
66+
trigger handler (Devvit retry storm). But the recovery path called
67+
`recordEvent` (which writes Redis). If the same blip was ongoing,
68+
the recovery 500'd anyway. Wrapped each recordEvent in its own try.
69+
70+
### Added — test gap close
71+
72+
- **`tests/lib/openaiErrors.test.ts`** (NEW, 11 tests) — pins both
73+
CRITICAL behaviours + user-config exclusions so a future "small
74+
tweak" can't re-introduce the substring or word-mismatch bugs.
75+
- **`tests/routes/api-auth.test.ts`** — added explicit wire-shape
76+
assertion `body === {ok:true, explanation: 'why'}` so the
77+
internal-Result-to-wire-envelope mapping at api.ts:259 is now
78+
test-pinned (previously only spy call counts were asserted).
79+
- **`tests/core/explain-event.test.ts`**`validateEventSummary`
80+
"accepts a well-formed event" now asserts `r.value === baseEvent`
81+
so a regression returning `{ok:true}` w/o the success field can't
82+
pass.
83+
84+
### Tests
85+
86+
505 → 516 passing (+11 classifier tests).
1087

1188
## [0.5.3] — 2026-05-18
1289

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "cm-devvit",
4-
"version": "0.5.3",
4+
"version": "0.5.4",
55
"description": "Devvit Web port of FoxxMD's ContextMod rule-engine moderation bot",
66
"license": "MIT",
77
"type": "module",

0 commit comments

Comments
 (0)