refactor(forms-api): declarative per-form spam policy#85
Merged
Conversation
Extract the spam-protection pipeline (recaptcha + honeypot + gibberish + timing) into a single declarative SpamPolicy passed to runSpamChecks, and extract the email-then-persist sequence (with emailSent/warning fallback) into sendAndPersist. Contact form gets the full policy; doula-match form gets recaptcha-only, matching observed spam volume. Records the reactive-per-form decision in ADR-0001 so future reviews do not "fix" the asymmetry by unifying the two policies. HTTP contract preserved; all 25 route tests pass unchanged.
- Wrap persist() in try/catch; log CRITICAL with emailSent+persistFailed when email succeeded but Firestore persist failed, then re-throw - Tighten SendAndPersistResult to a discriminated union and document the persist callback's idempotency contract - Use static log message prefixes in run-spam-checks (formType moved to structured metadata) and emit debug logs when honeypot/gibberish/ timing policy sections are not configured - Add route-level test exercising multi-field gibberish flagging
Auto-fix from eslint --fix to clear pre-existing @typescript-eslint/no-unnecessary-type-assertion errors that were failing CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
runSpamChecksdriven by a declarativeSpamPolicy(recaptcha + optional honeypot/gibberish/timing).emailSent/warningfallback intosendAndPersist.Why
Both handlers had near-duplicated plumbing, but the contact form had accumulated three extra spam checks (honeypot, gibberish, timing) that the match form lacks — because spam volume against the two forms differs. The duplication made the policy implicit (visible only in what code is/isn't there). After this refactor each handler reads as a single declarative object, and adding a check on one form is a one-line edit, not a cross-handler weave.
Test plan
bun test src/forms-api/routes/— all 25 route tests pass without modification (HTTP contract preserved)tsccleanbun run lint— no new lint errors in the touched files