Skip to content

meeting-api sweeps: webhook retries bump meeting.updated_at via SQLAlchemy autotouch — corrupts every consumer that filters on updated_at #327

@DmitriyG228

Description

@DmitriyG228

Symptom

meetings.updated_at is bumped on every UPDATE to the row, including writes that record webhook delivery state (data->'webhook_deliveries' append, retry counters, etc). A row with active webhook retries can have its `updated_at` advanced indefinitely, defeating any consumer that uses `updated_at < threshold` as a "stale" / "no-progress" predicate.

Concrete past failure

#313 — the Pack E.3.2 stale-stopping sweep used `Meeting.updated_at < threshold`. Meetings genuinely stuck in 'stopping' kept looking fresh because webhook retry workers updated `data->'webhook_deliveries'`, bumping `updated_at`, and the sweep never fired. v0.10.6.1 worked around this by switching the sweep to a derived `last_progress_at` from `data->'status_transition'`.

Why this is bigger than the sweep

Any other code that uses `meetings.updated_at` as a "domain progress" signal is silently wrong. Candidates:

  • analytics queries filtering meetings updated since N
  • third-party integrations that poll on `updated_at`
  • any future "stale" sweeps for other status states (joining / awaiting_admission / etc.)
  • change-data-capture downstream consumers

The semantics drift from "row last changed" (current behavior) to what most consumers actually want: "domain progress last changed".

Proposed fix options

A. Move webhook delivery state to a separate table. `webhook_deliveries` already exists in some shape — verify and migrate.
B. Mark webhook-retry-touched columns as not bumping `updated_at` at the model/SQL level (PG generated columns or trigger-based).
C. Add an explicit `last_domain_progress_at` column that is only bumped on status transitions / data shape changes, leave `updated_at` as the row-level last-write timestamp.

Option C is least invasive and most explicit; option A is cleanest long-term.

Urgency

Medium. v0.10.6.1 sweep predicate routes around the symptom for the stale-stopping case, but every other `updated_at`-based query in the codebase is a latent bug. Should land in v0.10.7 alongside any analytics work that touches meetings.

Acceptance

  • One of the three options chosen + implemented.
  • Pack E.3.2 sweep predicate reverts back to a single-column filter (no JSONB post-filter needed).
  • Registry check that catches future regressions: webhook write must not bump the chosen "domain progress" column.

Source

Surfaced during root-cause review of #313 in v0.10.6.1 develop stage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions