v1.2.0 Phase A: formatter migration, segment-aware SMS, redirect links, config validation #99

Merged
claude-bot merged 1 commit from feat/v1.2.0-phaseA-formatter into main 2026-07-18 19:57:10 +00:00
Contributor

First v1.2.0 slice — the notification formatting & channel-config cluster.

Formatter (#14, #15, F-32, #56)

  • Migrated Discord, Matrix, Signal, Pushover, Webex, and webhook onto the shared formatter with per-channel-type budgets and a 3-tier compose() (required / droppable-optional / pinned) that keeps one "Details" link in a predictable place (#15 decision 3). FormattedMessage now carries char/link/omitted/encoding/segment metadata.
  • Segment-aware SMS (#15/F-32): sanitize common non-GSM-7 punctuation (em-dash, curly quotes, ellipsis…), else budget at the UCS-2 rate so a stray non-GSM-7 char can't silently spill into an under-counted extra segment.
  • Twilio non-blocking (#56): the sync SDK call is offloaded via asyncio.to_thread.
  • New redirect_tokens table + an open-redirect-safe GET /r/{token} resolver — the 302 target is read only from the store, never the request; external hosts must be explicitly allowlisted (SHORT_LINK_EXTERNAL_ALLOWLIST).
  • Opt-in SHORT_LINK_REDIRECT_MODE wires notification links to mint {SHORT_LINK_BASE_URL}/r/{token} short links (deduped per target). Default off preserves the existing compact-base proxy model ({base}/p/{token}) — no behavior change unless you enable it.

Channel config validation (#55)

  • Per-type Pydantic config models reject malformed discord/matrix/pushover/webex/signal config at create/update (422 instead of a later send-time crash); defensive parsing in the notifiers turns malformed legacy config into a logged delivery failure, not an unhandled exception; name length caps mirror the DB columns.

Decisions recorded (#15)

Internal-only URL shortening (external passthrough unless allowlisted), type-level budgets (no per-instance override), one primary Details link.

Testing

Full suite green on the dev server: 544 passed (54 new tests). Migration 0019 applies cleanly through to head on real Postgres. discord/matrix/webex notifier paths verified with deps installed.

Closes #14, #15, #56, #13, #55

🤖 Generated with Claude Code

First v1.2.0 slice — the notification formatting & channel-config cluster. ## Formatter (#14, #15, F-32, #56) - Migrated **Discord, Matrix, Signal, Pushover, Webex, and webhook** onto the shared formatter with **per-channel-type budgets** and a 3-tier `compose()` (required / droppable-optional / pinned) that keeps **one "Details" link in a predictable place** (#15 decision 3). `FormattedMessage` now carries char/link/omitted/encoding/segment metadata. - **Segment-aware SMS** (#15/F-32): sanitize common non-GSM-7 punctuation (em-dash, curly quotes, ellipsis…), else budget at the UCS-2 rate so a stray non-GSM-7 char can't silently spill into an under-counted extra segment. - **Twilio non-blocking** (#56): the sync SDK call is offloaded via `asyncio.to_thread`. ## Links (#13 + #15 decision 1) - New `redirect_tokens` table + an **open-redirect-safe `GET /r/{token}`** resolver — the 302 target is read only from the store, never the request; external hosts must be explicitly allowlisted (`SHORT_LINK_EXTERNAL_ALLOWLIST`). - Opt-in **`SHORT_LINK_REDIRECT_MODE`** wires notification links to mint `{SHORT_LINK_BASE_URL}/r/{token}` short links (deduped per target). Default **off** preserves the existing compact-base proxy model (`{base}/p/{token}`) — no behavior change unless you enable it. ## Channel config validation (#55) - Per-type Pydantic config models reject malformed discord/matrix/pushover/webex/signal config at create/update (**422** instead of a later send-time crash); defensive parsing in the notifiers turns malformed *legacy* config into a logged delivery failure, not an unhandled exception; name length caps mirror the DB columns. ## Decisions recorded (#15) Internal-only URL shortening (external passthrough unless allowlisted), type-level budgets (no per-instance override), one primary Details link. ## Testing Full suite green on the dev server: **544 passed** (54 new tests). Migration `0019` applies cleanly through to head on real Postgres. discord/matrix/webex notifier paths verified with deps installed. Closes #14, #15, #56, #13, #55 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Notification formatter migration, segment-aware SMS, redirect short links, config validation
All checks were successful
CI / test (pull_request) Successful in 6m23s
1185a2217a
Formatter (#14, #15, F-32, #56):
- Migrate Discord, Matrix, Signal, Pushover, Webex, and webhook onto the shared
  notification formatter with per-channel-type budgets and a 3-tier compose()
  (required / droppable-optional / pinned) that keeps one "Details" link in a
  predictable place. FormattedMessage now carries char/link/omitted/encoding/
  segment metadata.
- SMS budgeting is segment-aware (#15/F-32): sanitize common non-GSM-7
  punctuation, else budget at the UCS-2 rate so a stray em-dash/curly-quote
  can't silently span extra segments.
- Twilio send offloaded to a thread (#56) so it no longer blocks the event loop.

Links (#13, #15 decisions): a redirect_tokens table + open-redirect-safe
`GET /r/{token}` resolver (target read only from the store, never the request;
external hosts must be allowlisted). Opt-in `SHORT_LINK_REDIRECT_MODE` wires
notification links to mint `{SHORT_LINK_BASE_URL}/r/{token}` short links; the
default keeps the existing compact-base proxy model.

Channel config validation (#55): per-type Pydantic config models rejecting
malformed discord/matrix/pushover/webex/signal config at create/update (422),
defensive parsing in the notifiers so malformed legacy config logs a delivery
failure instead of crashing, and name length caps.

Full suite green on the dev server: 544 passed; migration 0019 applies cleanly.

Closes #14, #15, #56, #13, #55

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude-bot deleted branch feat/v1.2.0-phaseA-formatter 2026-07-18 19:57:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/WeatherBot!99
No description provided.