Open shifts: post a gap once, even when the answer is lost and the post is retried

Merged
BI
1mo ago
+480-10

User description

Summary

Before: posting an open shift for an uncovered gap had no server-side guard against a retry. If the connection dropped after the server saved the post but before the answer arrived, tapping Post again created a second open shift and sent a second alert. Posts tied to an existing assignment were already protected by a unique index; gap posts were not.

After: every gap post carries a post key, one per date, generated by the post page and reused on retry. The server records each key against the open shift it created. A repeat with the same key returns the original open shift, marked as a replay, and sends no new alert. The same key from a different person is refused.

  • New migration 20261015133000_roster_open_post_once.sql: table roster_open_post_keys (service, key, open shift, RLS on, service role only) and function roster_open_post_once, which takes a per-service advisory lock, checks membership, and either replays the stored result or calls roster_command('open.post') and records the key.
  • App: a keyed post goes through the new function; the alert dispatcher skips replays; the post page keeps one key per date until the post succeeds.
  • schema.sql, the drift manifest, the migration seal, generated types, the demo store and the cross-tenant probe are updated to match.

Areas touched: Personal practice, Data platform and access, Knowledge and records

RAG impact: no retrieval behaviour change, roster only.

Verification

  • Checks run:
    • SQL behaviour test (tests/sql/roster-behaviour.sql) on Postgres 16, from the new schema.sql and from main's schema plus this migration: pass. Mutation checks (removing the key insert, removing the actor check) make it fail as expected.
    • test:focused on the changed files: 2519 passed. After rebasing on latest main: open-shifts, roster alerts and roster team API tests, 37 passed.
    • typecheck, eslint and prettier: clean.
    • check:migration-immutability: "310 shipped migrations unchanged". check:migration-role, check:function-grants, check:owner-scope, check:diff-integrity: pass.
  • Not run locally: the official drift:manifest regeneration needs the Postgres 17 Docker image, which this environment cannot run. The manifest entry was hand-computed, so CI's drift freshness check is the real proof. No browser test was run locally.

Risk and rollout

  • Risk: this is a database migration. Merging applies it to the live database within seconds. Merge only inside the approved window, with the owner's named yes to this specific change, per the database permission rule in AGENTS.md.
  • Rollback: a forward migration dropping roster_open_post_once and roster_open_post_keys; the app falls back to unkeyed posts only if the client change is also reverted.
  • Provider or production effects: none until merged. Keep this PR as a draft until the owner says yes.

Notes

  • No patient data. Work mode roster data only.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx

Generated by Claude Code

Summary by cubic

Stops a retried open-shift post for an uncovered gap from saving a second shift or sending a second alert when the first answer was lost.

  • The post page sends one random post key per date and reuses it on retry; roster_open_post_once remembers which open shift each key created.
  • A repeat with the same key returns the saved shift with replayed: true and sends no new alert; a key from another member is refused.
  • Migration 20261015174500_roster_open_post_once.sql adds roster_open_post_keys (service_role only, RLS on) and the function; unkeyed posts still go through roster_command unchanged.
  • Merging applies the migration to the live database within seconds; merge only inside the approved window.

Summary by Gitar

  • Alert delivery receipts:
    • Added roster_open_post_alerts to record when an open-shift post alert was delivered
    • Replays skip alerts only when a receipt exists, so a post whose alert work was killed still gets re-sent on retry
  • Second migration:
    • Added 20261015140000_roster_open_post_replay_receipts.sql creating the receipts table with RLS and service-role access

This will update automatically on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Retrying an open-shift post now reuses the original request, preventing duplicate shifts if the response was delayed or lost.
    • Replayed posts no longer send duplicate roster alerts. A fresh post still creates a new shift.

CodeAnt-AI Description

Prevent duplicate open shifts when a post is retried

What Changed

  • Retrying a post after its response is lost now returns the shift already created instead of creating another one.
  • A retry does not send the original post’s phone alerts again; a deliberate new post can still create another opening.

Impact

βœ… No duplicate open shifts after a lost response
βœ… No repeated phone alerts on retries

πŸ’‘ Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.