Skip to content

fix(expiry_reminder_email): default empty hours_remaining to "1" (bugbash P3-1) - #70

Merged
mastermanas805 merged 2 commits into
masterfrom
fix/anon-expiry-plural-empty
May 30, 2026
Merged

fix(expiry_reminder_email): default empty hours_remaining to "1" (bugbash P3-1)#70
mastermanas805 merged 2 commits into
masterfrom
fix/anon-expiry-plural-empty

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Summary

EMAIL-BUGBASH-WORKER-2026-05-19 P3-1: when hours_remaining is missing from the params map, the anon-expiry reminder email rendered a subject/body mismatch — subject said "expires in 1h" but the body said "expires in hours" (empty number, plural form because "" != "1").

Fix: default hours_remaining to "1" BEFORE deriving Plural, matching the subject's existing "1h" fallback (final-reminder cadence — safest default for a missing field is "last chance" not "blank").

Files

  • internal/jobs/expiry_reminder_email.go — default to "1" then derive Plural.
  • internal/jobs/expire_unexported_coverage_test.go — new TestRenderAnonExpiryEmail_EmptyHoursConsistency pins subject-vs-body parity across all four rendered surfaces (subject, html H2, html "Time left" row, text body) with a digit-precedes-hours invariant so any future surface that re-introduces hours with no count fails the test (rule 18 — registry-style guard).

Coverage block

Symptom:        " hours" / "1h vs (blank) hours" subject-body mismatch in anon expiry warnings
Enumeration:    rg -nE 'hours_remaining|Plural' internal/jobs/expir*
Sites found:    1 (renderAnonExpiryEmail in expiry_reminder_email.go)
Sites touched:  1
Coverage test:  TestRenderAnonExpiryEmail_EmptyHoursConsistency (digit-before-hours invariant)
Live verified:  unit only — Brevo sender unvalidated per CLAUDE.md P0

Test plan

  • make gate green locally (matches deploy.yml test step).
  • Targeted run: go test ./internal/jobs/ -run TestRenderAnonExpiryEmail -count=1 -short → ok.
  • CI green.

🤖 Generated with Claude Code

mastermanas805 and others added 2 commits May 30, 2026 22:16
…ubject and body agree

EMAIL-BUGBASH-WORKER-2026-05-19 P3-1: when hours_remaining was an empty
string in the params map, the subject defaulted to "expires in 1h" but
the body rendered "expires in  hours" — empty number, plural-true (since
`"" != "1"`). Recipients saw a contradictory subject vs body and a
grammatically broken " hours" with no count.

Default hours_remaining to "1" BEFORE deriving Plural, so the body
matches the subject's existing "1h" fallback (final-reminder cadence
is the safest default for a missing field — better to nudge as
last-chance than to render mysteriously empty copy).

Coverage: new TestRenderAnonExpiryEmail_EmptyHoursConsistency pins
subject-vs-body parity across all rendered surfaces (subject, html
H2, html "Time left" row, text body) and uses a digit-precedes guard
(`hasDigitBefore`) that fails if any future surface ever renders
" hours" with no preceding number. Registry-style invariant per rule
18 — catches the bug class, not just this exact site.

Symptom:        " hours" / "1h vs (blank) hours" subject-body mismatch in anon expiry warnings
Enumeration:    rg -nE 'hours_remaining|Plural' internal/jobs/expir*
Sites found:    1 (renderAnonExpiryEmail in expiry_reminder_email.go)
Sites touched:  1
Coverage test:  TestRenderAnonExpiryEmail_EmptyHoursConsistency (digit-before-hours invariant)
Live verified:  unit (no live email surface — Brevo sender still unvalidated per CLAUDE.md P0)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805
mastermanas805 merged commit 9da7faf into master May 30, 2026
11 checks passed
@mastermanas805
mastermanas805 deleted the fix/anon-expiry-plural-empty branch May 30, 2026 17:40
mastermanas805 added a commit that referenced this pull request Jun 6, 2026
…nts (#70) (#96)

The worker (PRODUCER) writes deployment_events autopsy rows in
upsertAutopsyRow; the api (CONSUMER) reads them back via
models.GetDeploymentEvents and serves GET /api/v1/deployments/:id/events.
The two live in SEPARATE Go modules (worker does NOT import the api), so
there is NO compiler-enforced link between the columns the worker INSERTs
and the columns/encoding the api SELECTs+unmarshals. A drift on either
side (renamed column, changed last_lines encoding, exit_code type flip)
would silently break the agent debug surface with no build error.

Existing tests cover that the worker WRITES the row
(deploy_failure_autopsy_test.go) and the api SERVES it (api PR #269's
deploy_autodebug_path_test.go + deploy_events_endpoint_test.go). This
adds the focused PARITY assertion:

  - TestAutopsySchemaParity_LastLinesEncodingMatchesAPIConsumer captures
    the EXACT last_lines bytes the worker binds (sqlmock Argument matcher)
    and runs the api's consumer logic (json.Unmarshal into []string) over
    them — if the worker ever changed the encoding away from
    json.Marshal([]string), the api /events handler would break and this
    reds first.

  - TestAutopsySchemaParity_ColumnSetMatchesAPIConsumer asserts the
    worker INSERTs exactly the column set the api Events handler SELECTs
    (deployment_id/kind/reason/exit_code/event/last_lines/hint, in order)
    via a regex over the INSERT — a column rename on either side reds.

Cross-ref: api PR #269 (docs/ci/02-FAILURE-DIAGNOSIS-AND-AUTODEBUG.md
§5.3). make gate green except a pre-existing local-only integration
flake outside this diff (jobs/TestIntegration_BillingReconciler_
SkipsTestCohort, billing-downgrade against the shared local DB). CI
(fresh DB) is authoritative; the new parity tests + all existing autopsy
tests pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant